《Android进阶之光》自定义ViewGroup实例:横向滑动HorizontalView

最近跟着《Android进阶之光》复习了自定义view,就来个自定义ViewGroup实例详解,运用了很多view相关的知识:

1、事件分发 与 滑动冲突解决

2、view的滑动,包括scrollBy、scrollTo、sroller

3、view的绘制流程:measure、layout、draw(这里不涉及)

 

HorizontalView:横向滑动的viewGroup,gif不会制作,你就想象成viewPager那种效果。

 

1、直接上 HorizontalView的代码,里面有详细的注释:


/**
 * 自定义viewGroup
 * 可横向滑动的viewGroup,类似viewPager
 */
public class HorizontalView extends ViewGroup {

    private static final String TAG = "hfy:HorizontalView";

    /**
     * 滑动辅助对象
     */
    private Scroller mScroller;

    /**
     * 速度追踪器
     */
    private VelocityTracker mVelocityTracker;

    /**
     * 记录父view在拦截之前的x、y
     */
    private int mLastXIntercept;
    private int mLastYIntercept;

    /**
     * 记录 父view 拦截时及之后的 开始的触摸x、y
     */
    private int mLastX;
    private int mLastY;

    /**
     * 当前的展示的子view下标
     */
    private int index;

    /**
     * 触摸横向移动距离
     */
    private int offsetXTotal;

    /**
     * 滑动小于 子view 宽度的一半
     */
    private boolean isScrollLessHalfWidth;

    public HorizontalView(Context context) {
        super(context);
        init();
    }

    public HorizontalView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

    private void init() {
        mScroller = new Scroller(getContext());
        mVelocityTracker = VelocityTracker.obtain();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //测量子view
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        //处理AT_MOST,宽:子view的宽和,高:第一个view的高
        if (getChildCount() == 0) {
            setMeasuredDimension(0, 0);
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            int childCount = getChildCount();
            View firstChild = getChildAt(0);
            setMeasuredDimension(childCount * firstChild.getMeasuredWidth(), firstChild.getMeasuredHeight());
        } else if (widthMode == MeasureSpec.AT_MOST) {
            int childCount = getChildCount();
            View firstChild = getChildAt(0);
            setMeasuredDimension(childCount * firstChild.getMeasuredWidth(), heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            View firstChild = getChildAt(0);
            setMeasuredDimension(widthSize, firstChild.getMeasuredHeight());
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int childCount = getChildCount();

        View child;
        int left = 0;
        //子view的布局
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            if ((child.getVisibility() != GONE)) {
                int width = child.getMeasuredWidth();
                int height = child.getMeasuredHeight();
                child.layout(left, 0, left + width, height);
                left += width;
            }
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
        //解决滑动冲突
        boolean isIntercept = false;

        int x = (int) motionEvent.getX();
        int y = (int) motionEvent.getY();

        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastXIntercept = x;
                mLastYIntercept = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //是横向的滑动
                boolean isHorizontal = Math.abs(x - mLastXIntercept) > Math.abs(y - mLastYIntercept);
                if (isHorizontal) {
                    //此处一旦拦截,后面的ACTION就不会在调用onInterceptTouchEvent
                    isIntercept = true;
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }

        Log.i(TAG, "onInterceptTouchEvent: isIntercept:" + isIntercept);

        //此处要记录下 拦截时的 开始的 触摸点(仅用于下面onTouchEvent处理滑动使用)
        mLastX = mLastXIntercept;
        mLastY = mLastYIntercept;

        mLastXIntercept = x;
        mLastYIntercept = y;

        return isIntercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int x = (int) event.getX();
        int y = (int) event.getY();

        int childMeasuredWidth = getChildAt(0).getMeasuredWidth();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //因在onInterceptTouchEvent中ACTION_DOWN返回false,所以这里不会走到。
                break;
            case MotionEvent.ACTION_MOVE:
                //触摸事件添加的速度追踪器,用于在UP时计算速度
                mVelocityTracker.addMovement(event);

                int offsetX = x - mLastX;
                offsetXTotal += offsetX;

                isScrollLessHalfWidth = Math.abs(offsetXTotal) < childMeasuredWidth / 2;
                滑动于子view 宽度的一半
                if (isScrollLessHalfWidth) {
                    //使所有子view进行水平滑动
                    scrollBy(-offsetX, 0);
                } else {
                    //滑动大于子view 宽度的一半,就直接 自动地 平滑地  完整地 滑过去。
                    if (offsetXTotal > 0) {
                        //不是第一个,就右滑,这里用To,因为子view的左边缘要滑到屏幕右边,负值表示子view要右滑(要理解mScrollX)
                        if (index > 0) {
                            smoothScrollTo(childMeasuredWidth * (--index), 0);
                        }
                    } else if (index < (getChildCount() - 1)) {
                        //左滑
                        smoothScrollTo(childMeasuredWidth * (++index), 0);
                    }

                    offsetXTotal = 0;
                }
                break;
            case MotionEvent.ACTION_UP:

                //滑动没有子view宽度一半 或者 第一个view右滑、最后的view左滑,那么 手指抬起时就滑回原位
                if (offsetXTotal > 0 && index == 0
                        || offsetXTotal < 0 && index == (getChildCount() - 1)) {
                    smoothScrollTo(childMeasuredWidth * (index), 0);
                } else if (isScrollLessHalfWidth) {

                    mVelocityTracker.computeCurrentVelocity(1000);
                    float xVelocity = mVelocityTracker.getXVelocity();
                    Log.i(TAG, "onTouchEvent: xVelocity=" + xVelocity);

                    if (Math.abs(xVelocity) > 50) {
                        //不是  第一个view右滑、最后的view左滑,也没有滑过宽度一半,但 速度很快,也 完整地 自动滑动过去。
                        if (xVelocity > 0) {
                            if (index > 0) {
                                smoothScrollTo(childMeasuredWidth * (--index), 0);
                            }
                        } else {
                            if (index < (getChildCount() - 1)) {
                                smoothScrollTo(childMeasuredWidth * (++index), 0);
                            }
                        }
                    } else {
                        //滑动没有子view宽度一半,速度也不快,那么 手指抬起时 也 滑回原位
                        smoothScrollTo(childMeasuredWidth * (index), 0);
                    }

                }

                //抬起 把横向滑动距离 置为0
                offsetXTotal = 0;

                mVelocityTracker.clear();
                break;
        }

        //这里需要记录上次的move的位置,因为scrollBy使所有字view滑动,本身不动,MotionEvent是一直改变的。
        mLastX = x;
        mLastY = y;

        Log.i(TAG, "onTouchEvent: index=" + index);
        return true;
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

    /**
     * 弹性滑动到某一位置
     *
     * @param destX 目标X
     * @param destY 目标Y
     */
    public void smoothScrollTo(int destX, int destY) {
        int scrollX = getScrollX();
        int scrollY = getScrollY();
        int offsetX = destX - scrollX;
        int offsetY = destY - scrollY;
        mScroller.startScroll(scrollX, scrollY, offsetX, offsetY, 600);
        invalidate();
    }
}

2、然后在xm使用,l如图:

我们自定义的HorizontalView里面放了3个ListView

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CustomViewGroupActivity">

    <com.hfy.demo00.logic.customview.HorizontalView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ListView
            android:id="@+id/lv_00"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorAccent">
        </ListView>
        <ListView
            android:id="@+id/lv_01"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </ListView>
        <ListView
            android:id="@+id/lv_02"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorPrimary">
        </ListView>
    </com.hfy.demo00.logic.customview.HorizontalView>

</RelativeLayout>

3、Activtiy对3个listView初始化,如下:

public class CustomViewGroupActivity extends AppCompatActivity {

    private ListView listView00;
    private ListView listView01;
    private ListView listView02;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_custom_view_group);

        initView();

    }

    private void initView() {
        listView00 = (ListView) findViewById(R.id.lv_00);
        listView01 = (ListView) findViewById(R.id.lv_01);
        listView02 = (ListView) findViewById(R.id.lv_02);

        String[] strs1 = {"1","2","4","5","6","1","2","4","5","6","1","2","4","5","6"};
        String[] strs2 = {"A","B","C","D","E","A","B","C","D","E","A","B","C","D","E"};
        String[] strs3 = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o"};
        ArrayAdapter adapter1 = new ArrayAdapter<String>(this, R.layout.support_simple_spinner_dropdown_item ,strs1);
        listView00.setAdapter(adapter1);

        ArrayAdapter adapter2 = new ArrayAdapter<String>(this, R.layout.support_simple_spinner_dropdown_item ,strs2);
        listView01.setAdapter(adapter2);

        ArrayAdapter adapter3 = new ArrayAdapter<String>(this, R.layout.support_simple_spinner_dropdown_item ,strs3);
        listView02.setAdapter(adapter3);

    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值