Android控件架構與自定義控件詳解(三)——自定義ViewGroup

ViewGroup存在的目的就是爲了對其子View進行管理,爲其子View添加顯示、響應的規則。因此,自定義ViewGroup通常需要重寫onMeasure()方法來對子View進行測量,重寫onLayout()方法來確定子View的位置,重寫onTouchEvent()方法增加響應事件。

本例將實現一個類似Android原生控件ScrollView的自定義ViewGroup,且在滑動的過程中,增加一個粘性的效果,效果圖如下:

這裏寫圖片描述

代碼如下:

package com.example.huangfei.myapplication;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * Created by huangfeihong on 2016/5/15.
 * 具有粘性效果的ScrollView
 * 先讓自定義ViewGroup實現類似ScrollView的功能,再添加粘性效果
 */
public class MyScrollView extends ViewGroup {
    private int mScreenHeight;
    private Scroller mScroller;
    private int mLastY;
    private int mStart;
    private int mEnd;

    public MyScrollView(Context context) {
        super(context);
        initView(context);
    }

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

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

    private void initView(Context context) {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        mScreenHeight = metrics.heightPixels; //讓每個子View都顯示完整的一屏
        mScroller = new Scroller(context);
    }

    /**
     * 先對其子View進行測量
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }



    /**
     * 對其子View進行放置位置的設定
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        // 設置ViewGroup的高度
        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
        mlp.height = childCount * mScreenHeight;
        setLayoutParams(mlp);
        //讓每個子View依次下排
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            if(childView.getVisibility() != View.GONE){
                childView.layout(l, i * mScreenHeight, r, (i + 1) * mScreenHeight);
            }
        }
    }

    /**
     * 在ViewGroup中添加滑動事件,可以使用scrollBy()方法來輔助滑動
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int y = (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mLastY = y;
                //記錄觸摸起點
                mStart = getScrollY();
                break;
            case MotionEvent.ACTION_MOVE:
                if(!mScroller.isFinished()){
                    mScroller.abortAnimation();
                }
                int dy = mLastY - y;
                if(getScrollY() < 0 && dy < 0)
                    dy = 0;
                if(getScrollY() > getHeight() - mScreenHeight  && dy > 0)
                    dy = 0;
                scrollBy(0, dy);
                mLastY = y;
                break;
            case MotionEvent.ACTION_UP:
                //記錄觸摸終點
                //mEnd = getScrollY();
                //int dScrollY = mEnd - mStart;
                int dScrollY = checkAlignment();
                //實現粘性效果,滑動距離大於子View的1/3,則使用Scroller類來平滑到下一個子View,否則就會回滾
                //到原來的位置
                if(dScrollY > 0){//向上滑動
                    if(dScrollY < mScreenHeight / 3){
                        mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
                    }else{
                        mScroller.startScroll(0, getScrollY(), 0, mScreenHeight - dScrollY);
                    }
                }else{//向下滑動
                    if(-dScrollY < mScreenHeight / 3){
                        mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
                    }else{
                        mScroller.startScroll(0, getScrollY(), 0, - mScreenHeight - dScrollY);
                    }
                }
                break;
        }
        postInvalidate();
        return true;
    }

    /**
     * 獲取手指滑動的距離
     * @return
     */
    private int checkAlignment() {
        int mEnd = getScrollY();
        boolean isUp = ((mEnd - mStart) > 0) ? true : false;
        int lastPrev = mEnd % mScreenHeight;
        int lastNext = mScreenHeight - lastPrev;
        if (isUp) {
            //向上的
            return lastPrev;
        } else {
            return -lastNext;
        }
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if(mScroller.computeScrollOffset()){
            scrollTo(0, mScroller.getCurrY());
            postInvalidate();
        }
    }
}

代碼地址

發佈了86 篇原創文章 · 獲贊 6 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章