android之自定义viewGroup仿scrollView的两种实现(滚动跟粘性)

来源:互联网 发布:通讯数据can总线 编辑:程序博客网 时间:2024/05/16 12:51

最近一直在研究自定义控件,一般大致分为三种情况:自绘控件,组合控件,继承控件。接下来我们来看下继承控件。在此借鉴一位博主的文章,补充粘性的实现效果,并且加注自己的一些理解。大家也可以查看原文博客。android之自定义viewGroup仿scrollView详解
直接上代码,注释的比较详细。可以通过Log的信息来观察下滑动时候坐标的变化,加深理解。

public class MyScrollViewGroup extends ViewGroup {    private Context mContext;    private int mScreenHeight;    private int totalHeight;    private Scroller mScroller;    public MyScrollViewGroup(Context context, AttributeSet attrs) {        super(context, attrs);        init(context);    }    public MyScrollViewGroup(Context context) {        super(context);        init(context);    }    private void init(Context context) {        mContext = context;        mScreenHeight = getScreenSize(mContext).heightPixels;        mScroller = new Scroller(mContext);    }    /***     * 获取真实的宽高 比如200px     *     * @param widthMeasureSpec     * @return     */    public int measureRealWidth(int widthMeasureSpec) {        int result = 200;        int specMode = MeasureSpec.getMode(widthMeasureSpec);        int realWidth = MeasureSpec.getSize(widthMeasureSpec);        switch (specMode) {            case MeasureSpec.EXACTLY:                //MeasureSpec.EXACTLY:精确值模式: 控件的layout_width或layout_heiht指定为具体值,比如200dp,或者指定为match_parent(占据父view的大小),系统返回的是这个模式                result = realWidth;                Log.d(TAG, "EXACTLY result " + result);                break;            case MeasureSpec.AT_MOST:                // MeasureSpec.AT_MOST: 最大值模式,控件的layout_width或layout_heiht指定为wrap_content时,控件大小一般随着控件的子控件或内容的变化而变化,此时控件的尺寸不能超过父控件                result = Math.max(result, realWidth);                Log.d(TAG, "AT_MOST result " + result);                break;            case MeasureSpec.UNSPECIFIED:                // MeasureSpec.UNSPECIFIED:不指定其大小测量模式,通常在绘制定义view的时候才会使用,即多大由开发者在onDraw()的时候指定大小                result = realWidth;                Log.d(TAG, "UNSPECIFIED result " + result);                break;        }        return result;    }    /***     * @param widthMeasureSpec  系统测量的宽 一共是32位的 高2位代表模式 低30位表示大小     * @param heightMeasureSpec 系统测量的高 一共是32位的 高2位代表模式 低30位表示大小     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        Log.d(TAG, "widthMeasureSpec " + widthMeasureSpec);        Log.d(TAG, "heightMeasureSpec " + heightMeasureSpec);        /***自身宽*/        int measureSelfWidth = measureRealWidth(widthMeasureSpec);        int measureSelfHeight = MeasureSpec.getSize(heightMeasureSpec);        Log.d(TAG, "widthMeasure " + measureSelfWidth);        Log.d(TAG, "widthMode " + MeasureSpec.getMode(widthMeasureSpec));        Log.d(TAG, "heightMeasure " + MeasureSpec.getSize(heightMeasureSpec));        Log.d(TAG, "heightMode " + MeasureSpec.getMode(heightMeasureSpec));        int childCount = getChildCount();        for (int i = 0; i < childCount; i++) {            View childView = getChildAt(i);            measureChild(childView, widthMeasureSpec, heightMeasureSpec);        }        //设置viewGroup的宽高,也可以在onlayout中通过layoutParams设置        totalHeight = getScreenSize(mContext).heightPixels * childCount;        setMeasuredDimension(measureSelfWidth, totalHeight);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        Log.d(TAG, "onLayout left " + l);        Log.d(TAG, "onLayout top " + t);        Log.d(TAG, "onLayout right " + r);        Log.d(TAG, "onLayout bottom " + b);        Log.d(TAG, "onLayout heightPixels " + getScreenSize(mContext).heightPixels);        int childCount = getChildCount();        for (int i = 0; i < childCount; i++) {            View childView = getChildAt(i);            childView.layout(l, i * mScreenHeight, r, (i + 1) * mScreenHeight);        }    }    private float lastDownY;    private float mScrollStart;    private float mScrollEnd;    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                lastDownY = event.getY();                mScrollStart = getScrollY();                Log.d(TAG, "totalHeight = " + totalHeight);                break;            case MotionEvent.ACTION_MOVE:                if (!mScroller.isFinished()) {                    mScroller.abortAnimation();                }                float currentY = event.getY();                float dy;                dy = lastDownY - currentY;                Log.d("test", "dy = " + dy);                Log.d("test", "getScrollY() = " + getScrollY());                Log.d("test", "getHeight()  = " + getHeight());                Log.d("test", "mScreenHeight()  = " + mScreenHeight);                Log.d("test", "getHeight() - mScreenHeight = " + (getHeight() - mScreenHeight));                if (getScrollY() < 0) {                    dy = 0;                    //最顶端,超过0时,不再下拉,要是不设置这个,getScrollY一直是负数                } else if (getScrollY() > getHeight() - mScreenHeight) {                    dy = 0;                    //滑到最底端时,不再滑动,要是不设置这个,getScrollY一直是大于getHeight() - mScreenHeight的数,无法再滑动                }                scrollBy(0, (int) dy);                //不断的设置Y,在滑动的时候子view就会比较顺畅                lastDownY = event.getY();                break;            case MotionEvent.ACTION_UP:                mScrollEnd = getScrollY();                int dScrollY = (int) (mScrollEnd - mScrollStart);                Log.d("test", "dScrollY = " + dScrollY);                //此处实现的是根据滑动的距离来实现滚动//                if (mScrollEnd < 0) {// 最顶端:手指向下滑动,回到初始位置//                    Log.d(TAG, "mScrollEnd < 0" + dScrollY);//                    mScroller.startScroll(0, getScrollY(), 0, -getScrollY());//                } else if (mScrollEnd > getHeight() - mScreenHeight) {//已经到最底端,手指向上滑动回到底部位置//                    Log.d(TAG, "getHeight() - mScreenHeight - (int) mScrollEnd " + (getHeight() - mScreenHeight - (int) mScrollEnd));//                    mScroller.startScroll(0, getScrollY(), 0, getHeight() - mScreenHeight - (int) mScrollEnd);//                }                //此处实现的是根据设定的距离,来实现粘性滑动的效果                if (dScrollY > 0) {                    //向上滑动dScrollY为正值                    if (dScrollY < mScreenHeight / 3) {                        mScroller.startScroll(0, getScrollY(), 0, -dScrollY);                    } else {                        mScroller.startScroll(0, getScrollY(), 0, (mScreenHeight - dScrollY));                    }                } else {                    //向下滑动dScrollY为负值                    if (-dScrollY < mScreenHeight / 3) {                        mScroller.startScroll(0, getScrollY(), 0, -dScrollY);                    } else {                        mScroller.startScroll(0, getScrollY(), 0, (-mScreenHeight - dScrollY));                    }                }                break;        }        postInvalidate();// 重绘执行computeScroll()        return true;//需要返回true否则down后无法执行move和up操作    }    /**     * Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是滑动辅助UI运动,反而是单纯地为滑动提供计算     * 需要invalidate()之后才会调用,这个方法在onDraw()中调用     */    @Override    public void computeScroll() {        super.computeScroll();        Log.d(TAG, "mScroller.getCurrY() " + mScroller.getCurrY());        if (mScroller.computeScrollOffset()) {//是否已经滚动完成            scrollTo(0, mScroller.getCurrY());//获取当前值,startScroll()初始化后,调用就能获取区间值            postInvalidate();        }    }    /**     * 获取屏幕大小,这个可以用一个常量不用每次都获取     *     * @param context     * @return     */    public static DisplayMetrics getScreenSize(Context context) {        DisplayMetrics metrics = new DisplayMetrics();        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);        wm.getDefaultDisplay().getMetrics(metrics);        return metrics;    }}

下面附上两种运行效果,比较下不同之处。
第一种
第二种

1 0