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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容