《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);

    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章