Android自定义控件:从零开始实现魅族flyme6应用市场应用详情弹出式layout

前几天无意中发现魅族flyme6应用市场的应用详情界面非常有意思,作为一枚程序员,看到有意思的东西怎么办?当然是想办法自己也整一个啦,哈哈。
废话不多说,下面先看看魅族flyme6应用市场详情页弹出时的界面,也就是我们最终的要实现的效果。
注:由于gif大小有限制,这里只是简单的演示一下效果,有魅族手机的大兄弟可以把玩把玩,还是很不错的(不是广告不是广告不是广告)。


这里写图片描述

讲道理,魅族的设计,个人还是觉得非常简约漂亮的。下面分步分析一下,有几个要实现的效果。

  1. 应用详情页弹出,非全屏;
  2. 应用详情页可以拖动;
  3. 向下拖动应用详情不超过一定距离回弹,超过一定距离弹出;
  4. 应用详情页拖动到顶部,内部的子view能够继续跟随手指滑动;
  5. 内部子view滚到定顶部之后,应用详情页能够继续跟随手指滑动;
  6. 应用详情页滑动后放手,内部的子view有一个惯性滑动效果;
  7. 应用详情页上滚到顶部,子view滚动超过一定距离之后,返回键的背景和应用名称会动态显示;

应用详情页弹出

由于有段时间没搞自定义控件了,最近项目搞得脑子都有点僵了。脑子里面一直想的都是2个fragment跳转,然后晚上突然灵机一动,这不对啊,这不就是一个弹出来的layout吗?万恶的惯性思维。
好的,基本思路已经确定,那么开撸,老规矩,先上一张“原理图”(请忽略我的画图工具)。

这里写图片描述

现在来分析一下,该用哪种layout实现。根据上面的魅族应用商城效果,很明显的看的到,无论contentview的位置怎么变,darkview始终都作为一个背景存在。所以我们这里采用继承FrameLayout来实现我们的自定义layout。其实继承其它的layout也能实现,但是个人觉得FrameLayout比较简单。

先重写几个关键的方法,初始化一下各个参数。

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //Log.e("tag", "onFinishInflate");
        if (getChildCount() != 2) {
            throw new IllegalArgumentException("only can 2 child in this view");
        } else {
            if (getChildAt(0) != null) {
                darkView = getChildAt(0);
            } else {
                throw new IllegalArgumentException("child(0) can not be null");
            }

            if (getChildAt(1) instanceof ViewGroup) {
                contentView = (LinearLayout) getChildAt(1);
                if (contentView.getChildAt(0) instanceof TitleBar) {
                    titleBar = (TitleBar) contentView.getChildAt(0);
                }

                if (contentView.getChildAt(1) instanceof MyScrollView) {
                    myScrollView = (MyScrollView) contentView.getChildAt(1);
                    myScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
                }
            } else {
                throw new IllegalArgumentException("child(1) must be extends ViewGroup");
            }
        }
    }

    /**
     * onMeasure执行完之后执行
     * 初始化自己和子View的宽高
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.e("tag", "onSizeChanged");
        mOrginY = titleBar.getMeasuredHeight() + UIUtils.getStatusBarHeight(contentView);
        mBottomY = contentView.getMeasuredHeight() + mOrginY;
        mDragRange = titleBar.getMeasuredHeight();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e("tag", "onMeasure");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.e("tag", "onLayout");
        contentView.layout(0, mBottomY, contentView.getMeasuredWidth(), mBottomY + contentView
                .getMeasuredHeight());
    }

我们知道,内容区域是可以滚动的,所以我们在内容区域添加一个自定义的scrollview,这个自定义的scrollview放到后面再说,布局代码偷了个懒,直接把前面的一篇文章 Android自定义控件:打造自己的QQ空间主页的listitem拿来凑数了,先看看我们现在实现的静态效果。

这里写图片描述

这里需要插一点其他知识,我们的模拟器状态栏默认字体是白色,我们App的背景也是白色,如果不做修改的话,状态栏是什么都看不到的,那么如何像现在这样高亮显示呢?魅族和小米手机可以直接使用下面的方法,Android6.0以上也支持通过设置style.xml中的android:windowLightStatusBar=true动态高亮显示。

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:windowLightStatusBar">true</item>
    </style>
</resources>

    /**
     * 设置魅族flyme状态栏dark颜色
     * @param activity activity
     * @param dark 是否dark
     * @return 设置结果
     */
    public static boolean setMeizuStatusBarDarkIcon(Activity activity, boolean dark) {
        boolean result = false;
        if (activity != null) {
            try {
                WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
                Field darkFlag = WindowManager.LayoutParams.class
                        .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
                Field meizuFlags = WindowManager.LayoutParams.class
                        .getDeclaredField("meizuFlags");
                darkFlag.setAccessible(true);
                meizuFlags.setAccessible(true);
                int bit = darkFlag.getInt(null);
                int value = meizuFlags.getInt(lp);
                if (dark) {
                    value |= bit;
                } else {
                    value &= ~bit;
                }
                meizuFlags.setInt(lp, value);
                activity.getWindow().setAttributes(lp);
                result = true;
            } catch (Exception e) {
            }
        }
        return result;
    }

    /**
     * 设置MIUI状态栏dark颜色
     * @param activity activity
     * @param darkmode 是否dark
     * @return 设置结果
     */
    public static boolean setMiuiStatusBarDarkMode(Activity activity, boolean darkmode) {
        Class<? extends Window> clazz = activity.getWindow().getClass();
        try {
            int darkModeFlag = 0;
            Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
            Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
            darkModeFlag = field.getInt(layoutParams);
            Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
            extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

静态的layout搞定,现在要有个弹出的效果,很简单,来2个属性动画。

    /**
     * 弹出内容区域
     */
    public void popup() {
        //        ObjectAnimator.ofFloat(contentView, "translationY", mBottomY, mOrginY)
        // .setDuration(500)
        //                .start();
        setVisibility(VISIBLE);
        ValueAnimator valueAnimator = ValueAnimator.ofInt(mBottomY, mOrginY);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int top = (int) animation.getAnimatedValue();
                contentView.layout(0, top, contentView.getMeasuredWidth(), mBottomY);
            }
        });
        valueAnimator.setDuration(300);
        valueAnimator.start();
    }

    /**
     * 隐藏内容区域
     */
    public void dismiss() {
        //        ObjectAnimator.ofFloat(contentView, "translationY", 0.f, mBottomY).setDuration
        // (500).start();
        ValueAnimator valueAnimator = ValueAnimator.ofInt(contentView.getTop(), mBottomY);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int top = (int) animation.getAnimatedValue();
                contentView.layout(0, top, contentView.getMeasuredWidth(), mBottomY + contentView
                        .getMeasuredHeight());
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                setVisibility(INVISIBLE);
                destoryCache();//dismiss时销毁数据和重置界面
            }
        });
        valueAnimator.setDuration(300);
        valueAnimator.start();
    }

关于这里为什么不直接用ObjectAnimator改变translationY值呢,可以看到一开始的时候我确实是通过ObjectAnimator实现的,但是后面使用viewDragHelper来拖动view判定位置的时候,由于translationY值被改变了,所以相对高度变了,那么viewDragHelper来拖动时候,判断顶部位置就会出错。所以这里直接使用valueAnimator动态的layout contentview。通过2个简单的属性动画,就实现了弹出和消失时的2个效果。

应用详情页的拖动

一提到拖动,首先想起的是什么?没错,就是ViewDragHelper,下面重写几个关键的方法。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return viewDragHelper.shouldInterceptTouchEvent(ev) | !mIsDragInTop |
                mIsScrollInTop;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        //由viewDragHelper处理touch事件
        viewDragHelper.processTouchEvent(event);

        //消费掉事件
        return true;
    }

    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        /**
         * 用于判断是否捕获当前child的触摸事件
         * @param child
         *              当前触摸的子View
         * @param pointerId
         * @return
         *          true:捕获并解析
         *          false:不处理
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == contentView;
        }

        /**
         * 获取view水平方向的拖拽范围,不能限制拖拽范围
         * @param child
         *          拖拽的child view
         * @return
         *          拖拽范围
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return super.getViewHorizontalDragRange(child);
        }


        /**
         * 获取view垂直方向的拖拽范围,不能限制拖拽范围
         * @param child
         *          拖拽的child view
         * @return
         *          拖拽范围
         */
        @Override
        public int getViewVerticalDragRange(View child) {
            return super.getViewVerticalDragRange(child);
        }
        
        /**
         * 控制child在水平方向的移动
         * @param child
         *              控制移动的view
         * @param left
         *              ViewDragHelper判断当前child的left改变的值
         * @param dx
         *              本次child水平方向移动的距离
         * @return
         *              child最终的left值
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return super.clampViewPositionHorizontal(child, left, dx);
        }

        /**
         * 控制child在垂直方向的移动
         * @param child
         *              控制移动的view
         * @param top
         *              ViewDragHelper判断当前child的top改变的值
         * @param dy
         *              本次child垂直方向移动的距离
         * @return
         *              child最终的top值
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            if (child == contentView) {
                //Log.e("tag", "mIsScrollInTop = " + mIsScrollInTop);
                //Log.e("tag", "mIsDragInTop = " + mIsDragInTop);

                if (top < UIUtils.getStatusBarHeight(child)) {
                    top = UIUtils.getStatusBarHeight(child);//固定住contentView的顶部
                    darkView.setBackgroundColor(Color.WHITE);//拖动到顶部时darkview背景设置白色
                } else {
                    darkView.setBackgroundResource(R.color.dark);//没有拖动到顶部时darkview背景设置暗色
                    mIsDragInTop = false;
                }
            }
            return top;
        }

        /**
         * child位置改变的时候执行,一般用来做其它子View的伴随移动
         *
         * @param changedView 位置改变的view
         * @param left        child当前最新的left
         * @param top         child当前最新的top
         * @param dx          本次水平移动的距离
         * @param dy          本次垂直移动的距离
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
        }

        /**
         * 手指抬起的时候执行
         *
         * @param releasedChild 当前抬起的child view
         * @param xvel          x方向移动的速度 负:向做移动 正:向右移动
         * @param yvel          y方向移动的速度
         */

        public void onViewReleased(View releasedChild, float xvel, float yvel) {
        }
    };

上面代码写完,就可以拖动contentview了,并且根据contentview拖动位置动态设置darkview的背景色。


这里写图片描述

回弹和消失

判断回弹和消失,也比较简单,在callaback的onViewReleased中判断一下即可,dismiss方法是刚才前面写好的属性动画。回弹的话使用 viewDragHelper.smoothSlideViewTo这个方法,要注意的是,如果想有smooth效果,需要重写自定义layout的computeScroll方法,在computeScroll方法中持续的更新smooth动画。

 @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if (contentView.getTop() - mOrginY > mDragRange) {//向下拖拽,超出拖拽限定距离
                dismiss();
            } else if (contentView.getTop() - mOrginY > 0) {//向下拖拽,但是没有超出拖拽限定距离
                springback();
            }
        }     
   @Override
    public void computeScroll() {
        if (viewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(PopupLayout.this);
        }
    }

效果如下:


这里写图片描述

应用详情页和内部的子view的滑动处理

可以看的到,在魅族flyme6.0应用详情界面中,手指未离开过屏幕,就是一只往上滑动,当应用详情界面到顶部之后,自动的滚动内部的子scrollview。

前面的几个效果实现起来都可以说比较简单,这个地方就稍微有点麻烦了,关于自定义ViewGroup事件的传递,这里就不着重介绍了,网上资料也挺多的,无非就是拦截-分发-消费事件,找个图琢磨琢磨也能弄清楚了。这里讲讲我一开始看到这个效果想到的实现思路。

contentview的拖动已经实现了,那么如何无缝切入到scrollview中去滚动呢?开始的时候我也以为很简单,不就是在onInterceptTouchEvent中判断一下scrollview是否到滚动到顶部了嘛,然后到顶了就不拦截,没到顶就拦截。想的是挺简单,但是实际呢?由于onInterceptTouchEvent只在我们手指按下的时候会触发一次,所以只要我们的手没有抬起来,不管我们的手指怎么拖动,当前自定义layout始终都会消费掉我们的touch事件,只有在我们将contentview滚动到顶部之后,抬起手再次拖动scrollview区域时,当前onInterceptTouchEvent才会再次判断是否要拦截事件,所以这样做的只能实现滑到顶,然后手抬起,再滑动,这样才能够滑动内部的scrollview。

很明显这不是我们想要的效果,折腾了很久, 首先想到的是重写scrollview的onTouchEvent,当前自定义layout不拦截事件,全部丢给子view去处理,当scrollview判断手势并根据手势处理返回值。想法是不错,但是却忽略了一个严重的问题,只要onTouchEvent的ACTION_DOWN触发,那么不管返回值如何,都不会上发事件了,这样我们的自定义layout根本就动不了。

OK,既然不能丢到子view去处理touch事件,那么是否能够直接在当前layout处理子view的滚动,从而达到无缝滚动子view的效果呢,很明显,这样是可行的。我们在自定义scrollview声明一个接口,将当前的状态提供给自定义layout,然后自定义layout根据scrollview接口的回调更新scrollview的状态,并且做scrollto操作。

package com.zyw.horrarndoo.popuplayout.view;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;

/**
 * Created by Horrarndoo on 2017/7/3.
 * 自定义scrollview,监听scrollview滑动到顶部事件
 */

public class MyScrollView extends ScrollView {
    private boolean isScrollToTop = true;
    private boolean isScrollToBottom = false;
    private OnScrollLimitListener mOnScrollLimitListener;

    public MyScrollView(Context context) {
        this(context, null);
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 设置ScrollView滑动到边界监听
     *
     * @param onScrollLimitListener ScrollView滑动到边界监听
     */
    public void setOnScrollLimitListener(OnScrollLimitListener onScrollLimitListener) {
        mOnScrollLimitListener = onScrollLimitListener;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (getScrollY() == 0) {//滑动到顶部
            isScrollToTop = true;
            isScrollToBottom = false;
            isScrollToBottom = false;
        } else if (getScrollY() + getHeight() - getPaddingTop() - getPaddingBottom() ==
                getChildAt(0).getHeight()) {
            // 小心踩坑: 这里不能是 >=
            // 小心踩坑:这里最容易忽视的就是ScrollView上下的padding 
            isScrollToTop = false;
            isScrollToBottom = true;
        } else {
            isScrollToTop = false;
            isScrollToBottom = false;
        }
        notifyScrollChangedListeners();
    }

    /**
     * 回调
     */
    private void notifyScrollChangedListeners() {
        if (isScrollToTop) {
            if (mOnScrollLimitListener != null) {
                mOnScrollLimitListener.onScrollTop();
            }
        } else if (isScrollToBottom) {
            if (mOnScrollLimitListener != null) {
                mOnScrollLimitListener.onScrollBottom();
            }
        }else {
            if (mOnScrollLimitListener != null) {
                mOnScrollLimitListener.onScrollOther();
            }
        }
    }

    /**
     * scrollview滑动到边界监听接口
     */
    public interface OnScrollLimitListener {
        /**
         * 滑动到顶部
         */
        void onScrollTop();

        /**
         * 滑动到顶部和底部之间的位置(既不是顶部也不是底部)
         */
        void onScrollOther();

        /**
         * 滑动到底部
         */
        void onScrollBottom();
    }
}

设置scrollview滑动边界监听

myScrollView.setOnScrollLimitListener(new MyScrollView.OnScrollLimitListener() {
                        @Override
                        public void onScrollTop() {
                            //Log.e("tag", "myScrollView is scroll in top.");
                            mIsScrollInTop = true;
                        }

                        @Override
                        public void onScrollOther() {
                            mIsScrollInTop = false;
                        }

                        @Override
                        public void onScrollBottom() {
                            //Log.e("tag", "myScrollView is scroll in bottom.");
                            mIsScrollInTop = false;
                        }
                    });

完善callback回调方法

        /**
         * 控制child在垂直方向的移动
         * @param child
         *              控制移动的view
         * @param top
         *              ViewDragHelper判断当前child的top改变的值
         * @param dy
         *              本次child垂直方向移动的距离
         * @return
         *              child最终的top值
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            if (child == contentView) {
                //Log.e("tag", "mIsScrollInTop = " + mIsScrollInTop);
                //Log.e("tag", "mIsDragInTop = " + mIsDragInTop);
                mCurrentScrollY = myScrollView.getScrollY();
                if (!mIsScrollInTop && mIsDragInTop) {//如果ScrollView没有滑动到top并且contentView已经拖拽到顶部
                    top = UIUtils.getStatusBarHeight(child);//固定住contentView的顶部
                    mCurrentScrollY -= dy;//手指向下滑dy>0,要让scrollview向上滚动,所以scrollY应该减去dy
                    myScrollView.scrollTo(0, mCurrentScrollY);//滑动ScrollView
                    return top;
                }

                if (top < UIUtils.getStatusBarHeight(child)) {
                    top = UIUtils.getStatusBarHeight(child);//固定住contentView的顶部
                    darkView.setBackgroundColor(Color.WHITE);//拖动到顶部时darkview背景设置白色
                    mCurrentScrollY -= dy;//手指向上滑dy<0,要让scrollview向下滚动,所以scrollY应该减去dy
                    myScrollView.scrollTo(0, mCurrentScrollY);
                    mIsDragInTop = true;
                } else {
                    darkView.setBackgroundResource(R.color.dark);//没有拖动到顶部时darkview背景设置暗色
                    mIsDragInTop = false;
                }
            }
            return top;
        }

效果基本差不多,见图


这里写图片描述

子ScrollView惯性滑动解决

我们知道,scrollview是有个惯性滑动的,就是说根据你的手指滑动速度,在手指松开后,依然会顺着滑动方向滑动一段距离,按照我们目前的做法,由于是在自定义layout中设置scrollview的scrollto来的设置scrollview的滚动,所以肯定是没有办法惯性滑动的。
那如何实现呢?scrollview有一个fling方法,这个方法需要传入一个初速度值,这个初速度值怎么求呢?参考这篇博主的方法:http://blog.csdn.net/xk3440395/article/details/53039645,实际使用时手指上滑稍微有一点点问题,我在这里做了一点点更正。

    /**
     * 通过目标y得到需要scrollview fling需要的初速度
     *
     * @param endY       目标Y
     * @param isScrollUp scrollView是否向上滚动
     * @return 滑动初速度
     */
    private int getVelocityY(int endY, boolean isScrollUp) {
        int signum = isScrollUp ? -1 : 1;//上滚-1下,滚1,scrollview滚动方向和手指滑动方向相反
        //Log.e("tag", "mCurrentScrollY = " + mCurrentScrollY);
        //Log.e("tag", "endY = " + endY);
        double dis = (endY - mCurrentScrollY) * signum;
        double g = Math.log(dis / ViewConfiguration.getScrollFriction() / (SensorManager
                .GRAVITY_EARTH // g (m/s^2)
                * 39.37f // inch/meter
                * (getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f)) * Math.log(0.9) * ((float) (Math.log(0.78) / Math.log(0.9)) - 1.0) /
                (Math.log(0.78));
        return (int) (Math.exp(g) / 0.35f * (ViewConfiguration.getScrollFriction() *
                SensorManager.GRAVITY_EARTH
                * 39.37f // inch/meter
                * (getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f)) * signum;
    }

求初速度的方法搞定了,那么如何求解最终Y点位置呢,这里稍微偷了下懒,直接在onViewReleased方法中根据yvel值大概求了一个值。

        /**
         * 手指抬起的时候执行
         *
         * @param releasedChild 当前抬起的child view
         * @param xvel          x方向移动的速度 负:向做移动 正:向右移动
         * @param yvel          y方向移动的速度
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if (contentView.getTop() - mOrginY > mDragRange) {//向下拖拽,超出拖拽限定距离
                dismiss();
            } else if (contentView.getTop() - mOrginY > 0) {//向下拖拽,但是没有超出拖拽限定距离
                springback();
            }

            if (mIsDragInTop) {//contentView已经到顶部
                boolean isScrollUp = yvel > 0;//判断scroll滚动方向
                int endY = (int) (mCurrentScrollY - yvel / 4);//根据Y方向滚动速度和当前Y点求出最终结束的Y点
                myScrollView.fling(getVelocityY(endY, isScrollUp));//ScrollView滚动到结束Y点
                mCurrentScrollY = 0;//更新当前scrollY值
            }
        }
    };

在看看有没有惯性滚动的效果,嘿嘿,还不错,感觉还挺流畅的。

这里写图片描述

结合titlebar更新text和back键显示

其实效果里面已经看到了,我们的titlebar也是一个自定义linearlayout,并且在代码中暴露一个设置返回键背景和一个设置text显示的方法,实际代码比较简单,这里没有什么贴出来的必要了,有兴趣的直接看本文最后的源码就可以了。

写在最后

看起来好像效果也差不多,但是最终还是发现了最大的问题,其实还是事件拦截和处理的问题,在contentview没有滚动到顶部的时候,事件全部在本layout处理,子view在一开始只是被动的被自定义layout设置属性而已,实际的事件都被自定义layout消费掉了。所以只有在contentview滚动到顶部,子view才能拿到touch事件,也就是说我们现在的自定义layout,只有在滚动顶部之后,子控件才能正确的消费点击之类的事件。

这里写图片描述

关于如何处理这个问题,由于时间和工作的原因,暂时没有想出太好的解决方案。解决方案肯定会有,因为人家魅族都实现了嘛。

最后总结一句话:革命尚未成功,同志仍需努力,加油!

附上完整代码:https://github.com/Horrarndoo/PopupLayout

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容