第3章 Android控件架構(gòu)與自定義控件詳解

本章內(nèi)容預(yù)覽:

3.1 Android控件架構(gòu)
3.2 View的測(cè)量
3.3 View的繪制
3.4 ViewGroup的測(cè)量
3.5 ViewGroup的繪制
3.6 自定義View
??????3.6.1 對(duì)現(xiàn)有控件進(jìn)行拓展
??????3.6.2 創(chuàng)建復(fù)合控件
??????3.6.2 重寫View來實(shí)現(xiàn)全新的控件
3.7 自定義ViewGroup
3.8 事件攔截機(jī)制分析

正文

3.1 Android控件架構(gòu)

Android的每個(gè)控件都是占一塊矩形的區(qū)域,大致的分兩類,View和ViewGroup,ViewGroup控件作為父控件可以包含多個(gè)View控件,并管理其包含的View控件整個(gè)界面上的控件形成了一個(gè)樹形結(jié)構(gòu),也就是我們常說的控件樹,上層控件負(fù)責(zé)下層控件的測(cè)量和繪制,并且傳遞交互事件,通過findviewbyid()這個(gè)方法來獲取,其實(shí)就是遍歷查找,在樹形圖的頂部都有一個(gè)ViewParent對(duì)象,這就是控制核心,所有的交互管理事件都是由它統(tǒng)一調(diào)度和分配,從而進(jìn)行整個(gè)視圖的控制

image.png

image.png

我們可以看到,每個(gè)activity都有一個(gè)window對(duì)象,在Android中,window對(duì)象通常由一個(gè)phonewindow去實(shí)現(xiàn)的,phonewindow將一個(gè)DecorView設(shè)置為整個(gè)窗口的根View,DecorView作為窗口界面的頂層視圖,封裝了一些通用的方法,可以說,DecorView將要顯示的內(nèi)容都給了phonewindow,這里面所有的View監(jiān)聽,都是通過winsowmanagerService來接收的,通過相應(yīng)的回調(diào)來OnClicListener,在顯示上,他將屏幕分成了兩部分,一個(gè)title一個(gè)content,看到這里,大家應(yīng)該能看到一個(gè)熟悉的界面ContentView,它是一個(gè)ID為content分framelayout,activity_main.xml就是設(shè)置在這個(gè)framelayout里面

image.png

在代碼中當(dāng)程序onCreate()時(shí),也就設(shè)置了layout,執(zhí)行完后,activitymanagerservice會(huì)直接調(diào)用onResume,這個(gè)時(shí)候系統(tǒng)會(huì)把整個(gè)DecorView添加到phonewindow,從而最終完成界面的繪制。

3.2 View的測(cè)量

Android系統(tǒng)在繪制View前,必須先對(duì)View進(jìn)行測(cè)量,即告訴系統(tǒng)該畫一個(gè)多大的View。這個(gè)過程在onMeasure( )中進(jìn)行

MeasureSpec:

幫助測(cè)量View
MeasureSpec是一個(gè)32位的int值,高2位為測(cè)量的模式,低30位為測(cè)量的大小.

測(cè)量的模式:
  • EXACTLY:精確值模式
    當(dāng)layout_width或layout_height屬性值為特定值,或者為match_parent,則系統(tǒng)使用的是EXACTY
  • AT_MOST:最大值模式
    當(dāng)layout_width或layout_height屬性值為wrap_content時(shí),此時(shí)控件的尺寸不超過父控件允許的最大尺寸
  • UNSPECIFIED:
    不指定大小,View想要多大就多大,通常情況下在繪制自定義View時(shí)才使用。
    注意: View默認(rèn)的onMeasure()只支持EXACTLY模式,(具體值,match_parent),如果自定義View想要支持wrap_content屬性,則應(yīng)該重新onMeasure()來指定wrap_content時(shí)的大小。
如何進(jìn)行View的測(cè)量
  1. 重寫onMeasure()方法,把測(cè)量到的寬高值作為參數(shù)傳遞給setMeasuredDimension()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
}

如何自定義測(cè)量值

        int specMode=MeasureSpec.getMode(measureSpec);
        int specSize=MeasureSpec.getSize(measureSpec);

當(dāng)specMode為EXACTLY,直接使用指定的specSize,當(dāng)specMode為其它兩種模式,需要給它默認(rèn)的大小。特別當(dāng)指定wrap_content屬性,即AT_MOST模式,則需要取出我們指定的大小和sepcSize中最小的一個(gè)來作為最終的觀測(cè)值。
模板代碼:

   private int measureWidth(int measureSpec) {
        int result=0;
        int specMode=MeasureSpec.getMode(measureSpec);
        int specSize=MeasureSpec.getSize(measureSpec);
        if(specMode==MeasureSpec.EXACTLY){
            result=specSize;
            Log.e("measureWidth","MeasureSpec.EXACTLY");
        }else {
            result=200;
            if(specMode==MeasureSpec.AT_MOST){
                result=Math.min(200,specSize);
                Log.e("measureWidth","MeasureSpec.AT_MOST");
            }
        }
        return result;
    }

運(yùn)行結(jié)果展示:

1.1
image.png
image.png

1.2 全屏顯示:
image.png

image.png

1.3
image.png
image.png
image.png

1.4
image.png

image.png

image.png

1.5
image.png
image.png

image.png

3.3 View的繪制

重寫onDraw()方法

3.4 ViewGroup的測(cè)量

當(dāng)ViewGroup的大小為wrap_content,ViewGroup需要對(duì)所有子View進(jìn)行遍歷(從而調(diào)用子View的onMeasure()方法來獲得每個(gè)子View的測(cè)量值),以便獲得所有子View的大小,從而確定自己的大小。
而在其他模式下則通過具體的指定值來確定自身大小。
重寫onLayout()擺放子View的位置

3.5 ViewGroup的繪制

ViewGroup在一般情況下是不會(huì)繪制的,因?yàn)樗旧頉]有需要繪制的東西,如果不是指定ViewGroup的背景顏色,他連onDraw()都不會(huì)調(diào)用,但是ViewGroup會(huì)使用dispatchDraw()來繪制其他子View,其過程同樣是遍歷所喲普的子View,并調(diào)用子View的繪制方法來完成繪制的

3.6 自定義View

View中重要的回調(diào)方法:

  • onFinishInflate():從XML加載組件后回調(diào)
  • onSizeChanged():組件大小改變時(shí)回調(diào)
  • onMeasure():回調(diào)該方法來進(jìn)行測(cè)量
  • onLayout():回調(diào)該方法來確定顯示的位置
  • onTouchEvent():監(jiān)聽到觸摸事件時(shí)回調(diào)

實(shí)現(xiàn)自定義View的三種方式:

  • 對(duì)現(xiàn)有控件進(jìn)行拓展
  • 通過組合實(shí)現(xiàn)新的控件
  • 重寫View實(shí)現(xiàn)新的控件
3.6.1 對(duì)現(xiàn)有控件進(jìn)行拓展

一般通過重寫onDraw()方法

    @Override
    protected void onDraw(Canvas canvas) {
        //在回調(diào)父類之前,實(shí)現(xiàn)自己的邏輯,對(duì)textview來說就是繪制文本內(nèi)容前
        super.onDraw(canvas);
         //在回調(diào)父類之后,實(shí)現(xiàn)自己的邏輯,對(duì)textview來說就是繪制文本內(nèi)容后
     }
3.6.2 創(chuàng)建復(fù)合控件

創(chuàng)建一個(gè)復(fù)合控件可以很好的創(chuàng)建出具有重要功能的控件集合,這種方式經(jīng)常需要繼承一個(gè)合適的ViewGroup,再給他添加指定功能的控件,從而組成一個(gè)新的合適的控件,通過這種方式創(chuàng)建的控件,我們一般都會(huì)給他指定的一些屬性,讓他具有更強(qiáng)的擴(kuò)展性,下面就以一個(gè)TopBar為例子,講解如何創(chuàng)建復(fù)合控件

3.6.2.1 定義屬性

res --- values --- attrs.xml

    <declare-styleable name="TopBar">
        <attr name="title" format="string" />
        <attr name="titleTextSize" format="dimension" />
        <attr name="titleTextColor" format="color" />
        <attr name="leftTextColor" format="color" />
        <attr name="leftBackground" format="reference|color" />
        <attr name="leftText" format="string" />
        <attr name="rightTextColor" format="color" />
        <attr name="rightBackground" format="reference|color" />
        <attr name="rightText" format="string" />
    </declare-styleable>
3.6.2.2 獲取屬性值
        // 通過這個(gè)方法,將你在atts.xml中定義的declare-styleable
        // 的所有屬性的值存儲(chǔ)到TypedArray中
        TypedArray ta = context.obtainStyledAttributes(attrs,
                R.styleable.TopBar);
        // 從TypedArray中取出對(duì)應(yīng)的值來為要設(shè)置的屬性賦值
        mLeftTextColor = ta.getColor(
                R.styleable.TopBar_leftTextColor, 0);
3.6.2.3 組合控件
        mLeftButton = new Button(context);
        mRightButton = new Button(context);
        mTitleView = new TextView(context);

        // 為創(chuàng)建的組件元素賦值
        // 值就來源于我們?cè)谝玫膞ml文件中給對(duì)應(yīng)屬性的賦值
        mLeftButton.setTextColor(mLeftTextColor);
        mLeftButton.setBackground(mLeftBackground);
        mLeftButton.setText(mLeftText);

        mRightButton.setTextColor(mRightTextColor);
        mRightButton.setBackground(mRightBackground);
        mRightButton.setText(mRightText);

        mTitleView.setText(mTitle);
        mTitleView.setTextColor(mTitleTextColor);
        mTitleView.setTextSize(mTitleTextSize);
        mTitleView.setGravity(Gravity.CENTER);

        // 為組件元素設(shè)置相應(yīng)的布局元素
        mLeftParams = new LayoutParams(
                LayoutParams.WRAP_CONTENT,
                LayoutParams.MATCH_PARENT);
        mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
        // 添加到ViewGroup
        addView(mLeftButton, mLeftParams);

        mRightParams = new LayoutParams(
                LayoutParams.WRAP_CONTENT,
                LayoutParams.MATCH_PARENT);
        mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);
        addView(mRightButton, mRightParams);

        mTitlepParams = new LayoutParams(
                LayoutParams.WRAP_CONTENT,
                LayoutParams.MATCH_PARENT);
        mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
        addView(mTitleView, mTitlepParams);

3.6.2.4 定義接口
    // 接口對(duì)象,實(shí)現(xiàn)回調(diào)機(jī)制,在回調(diào)方法中
    // 通過映射的接口對(duì)象調(diào)用接口中的方法
    // 而不用去考慮如何實(shí)現(xiàn),具體的實(shí)現(xiàn)由調(diào)用者去創(chuàng)建
    public interface topbarClickListener {
        // 左按鈕點(diǎn)擊事件
        void leftClick();
        // 右按鈕點(diǎn)擊事件
        void rightClick();
    }
3.6.2.5 暴露接口給調(diào)用者 實(shí)現(xiàn)接口回調(diào) 提供public方法設(shè)置
     // 暴露一個(gè)方法給調(diào)用者來注冊(cè)接口回調(diào)
    // 通過接口來獲得回調(diào)者對(duì)接口方法的實(shí)現(xiàn)
    public void setOnTopbarClickListener(topbarClickListener mListener) {
        this.mListener = mListener;
    }
3.6.2.6 引用UI模板

在布局文件中引入命名空間:
xmlns:custom="http://schemas.android.com/apk/res-auto"

<com.imooc.systemwidget.TopBar
        android:id="@+id/topBar"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        custom:leftBackground="@drawable/blue_button"
        custom:leftText="Back"
        custom:leftTextColor="#FFFFFF"
        custom:rightBackground="@drawable/blue_button"
        custom:rightText="More"
        custom:rightTextColor="#FFFFFF"
        custom:title="自定義標(biāo)題"
        custom:titleTextColor="#123412"
        custom:titleTextSize="10sp"/>
public class TopBar extends RelativeLayout {

    // 包含topbar上的元素:左按鈕、右按鈕、標(biāo)題
    private Button mLeftButton, mRightButton;
    private TextView mTitleView;

    // 布局屬性,用來控制組件元素在ViewGroup中的位置
    private LayoutParams mLeftParams, mTitlepParams, mRightParams;

    // 左按鈕的屬性值,即我們?cè)赼tts.xml文件中定義的屬性
    private int mLeftTextColor;
    private Drawable mLeftBackground;
    private String mLeftText;
    // 右按鈕的屬性值,即我們?cè)赼tts.xml文件中定義的屬性
    private int mRightTextColor;
    private Drawable mRightBackground;
    private String mRightText;
    // 標(biāo)題的屬性值,即我們?cè)赼tts.xml文件中定義的屬性
    private float mTitleTextSize;
    private int mTitleTextColor;
    private String mTitle;

    // 映射傳入的接口對(duì)象
    private topbarClickListener mListener;

    public TopBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public TopBar(Context context) {
        super(context);
    }

    public TopBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 設(shè)置topbar的背景
        setBackgroundColor(0xFFF59563);
        // 通過這個(gè)方法,將你在atts.xml中定義的declare-styleable
        // 的所有屬性的值存儲(chǔ)到TypedArray中
        TypedArray ta = context.obtainStyledAttributes(attrs,
                R.styleable.TopBar);
        // 從TypedArray中取出對(duì)應(yīng)的值來為要設(shè)置的屬性賦值
        mLeftTextColor = ta.getColor(
                R.styleable.TopBar_leftTextColor, 0);
        mLeftBackground = ta.getDrawable(
                R.styleable.TopBar_leftBackground);
        mLeftText = ta.getString(R.styleable.TopBar_leftText);

        mRightTextColor = ta.getColor(
                R.styleable.TopBar_rightTextColor, 0);
        mRightBackground = ta.getDrawable(
                R.styleable.TopBar_rightBackground);
        mRightText = ta.getString(R.styleable.TopBar_rightText);

        mTitleTextSize = ta.getDimension(
                R.styleable.TopBar_titleTextSize, 10);
        mTitleTextColor = ta.getColor(
                R.styleable.TopBar_titleTextColor, 0);
        mTitle = ta.getString(R.styleable.TopBar_title);

        // 獲取完TypedArray的值后,一般要調(diào)用
        // recyle方法來避免重新創(chuàng)建的時(shí)候的錯(cuò)誤
        ta.recycle();

        mLeftButton = new Button(context);
        mRightButton = new Button(context);
        mTitleView = new TextView(context);

        // 為創(chuàng)建的組件元素賦值
        // 值就來源于我們?cè)谝玫膞ml文件中給對(duì)應(yīng)屬性的賦值
        mLeftButton.setTextColor(mLeftTextColor);
        mLeftButton.setBackground(mLeftBackground);
        mLeftButton.setText(mLeftText);

        mRightButton.setTextColor(mRightTextColor);
        mRightButton.setBackground(mRightBackground);
        mRightButton.setText(mRightText);

        mTitleView.setText(mTitle);
        mTitleView.setTextColor(mTitleTextColor);
        mTitleView.setTextSize(mTitleTextSize);
        mTitleView.setGravity(Gravity.CENTER);

        // 為組件元素設(shè)置相應(yīng)的布局元素
        mLeftParams = new LayoutParams(
                LayoutParams.WRAP_CONTENT,
                LayoutParams.MATCH_PARENT);
        mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
        // 添加到ViewGroup
        addView(mLeftButton, mLeftParams);

        mRightParams = new LayoutParams(
                LayoutParams.WRAP_CONTENT,
                LayoutParams.MATCH_PARENT);
        mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);
        addView(mRightButton, mRightParams);

        mTitlepParams = new LayoutParams(
                LayoutParams.WRAP_CONTENT,
                LayoutParams.MATCH_PARENT);
        mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
        addView(mTitleView, mTitlepParams);

        // 按鈕的點(diǎn)擊事件,不需要具體的實(shí)現(xiàn),
        // 只需調(diào)用接口的方法,回調(diào)的時(shí)候,會(huì)有具體的實(shí)現(xiàn)
        mRightButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                mListener.rightClick();
            }
        });

        mLeftButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                mListener.leftClick();
            }
        });
    }

    // 暴露一個(gè)方法給調(diào)用者來注冊(cè)接口回調(diào)
    // 通過接口來獲得回調(diào)者對(duì)接口方法的實(shí)現(xiàn)
    public void setOnTopbarClickListener(topbarClickListener mListener) {
        this.mListener = mListener;
    }

    /**
     * 設(shè)置按鈕的顯示與否 通過id區(qū)分按鈕,flag區(qū)分是否顯示
     *
     * @param id   id
     * @param flag 是否顯示
     */
    public void setButtonVisable(int id, boolean flag) {
        if (flag) {
            if (id == 0) {
                mLeftButton.setVisibility(View.VISIBLE);
            } else {
                mRightButton.setVisibility(View.VISIBLE);
            }
        } else {
            if (id == 0) {
                mLeftButton.setVisibility(View.GONE);
            } else {
                mRightButton.setVisibility(View.GONE);
            }
        }
    }

    // 接口對(duì)象,實(shí)現(xiàn)回調(diào)機(jī)制,在回調(diào)方法中
    // 通過映射的接口對(duì)象調(diào)用接口中的方法
    // 而不用去考慮如何實(shí)現(xiàn),具體的實(shí)現(xiàn)由調(diào)用者去創(chuàng)建
    public interface topbarClickListener {
        // 左按鈕點(diǎn)擊事件
        void leftClick();
        // 右按鈕點(diǎn)擊事件
        void rightClick();
    }
3.6.3 重寫View實(shí)現(xiàn)全新的控件

弧線展示圖:


image.png
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) {
        WindowManager wm = (WindowManager) context.getSystemService(
                Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        mScreenHeight = dm.heightPixels;
        mScroller = new Scroller(context);
    }

    @Override
    protected void onLayout(boolean changed,
                            int l, int t, int r, int b) {
        int childCount = getChildCount();
        // 設(shè)置ViewGroup的高度
        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
        mlp.height = mScreenHeight * childCount;
        setLayoutParams(mlp);
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                child.layout(l, i * mScreenHeight,
                        r, (i + 1) * mScreenHeight);
            }
        }
    }

    @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);
        }
    }

    @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;
                }
                if (getScrollY() > getHeight() - mScreenHeight) {
                    dy = 0;
                }
                scrollBy(0, dy);
                mLastY = y;
                break;
            case MotionEvent.ACTION_UP:
                mEnd = getScrollY();
                int dScrollY = mEnd - mStart;
                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;
    }

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

音頻條形圖


image.png
public class VolumeView extends View {

    private int mWidth;
    private int mRectWidth;
    private int mRectHeight;
    private Paint mPaint;
    private int mRectCount;
    private int offset = 5;
    private double mRandom;
    private LinearGradient mLinearGradient;

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

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

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

    private void initView() {
        mPaint = new Paint();
        mPaint.setColor(Color.BLUE);
        mPaint.setStyle(Paint.Style.FILL);
        mRectCount = 12;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = getWidth();
        mRectHeight = getHeight();
        mRectWidth = (int) (mWidth * 0.6 / mRectCount);
        mLinearGradient = new LinearGradient(
                0,
                0,
                mRectWidth,
                mRectHeight,
                Color.YELLOW,
                Color.BLUE,
                Shader.TileMode.CLAMP);
        mPaint.setShader(mLinearGradient);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < mRectCount; i++) {
            mRandom = Math.random();
            float currentHeight = (float) (mRectHeight * mRandom);
            canvas.drawRect(
                    (float) (mWidth * 0.4 / 2 + mRectWidth * i + offset),
                    currentHeight,
                    (float) (mWidth * 0.4 / 2 + mRectWidth * (i + 1)),
                    mRectHeight,
                    mPaint);
        }
        postInvalidateDelayed(300);
    }
}

3.7 自定義ViewGroup

這個(gè)管理子View的管理者,我們來定義一下,通常我們自定義ViewGroup是需要onMeasure()來測(cè)量的,然后重寫onLayout()來確定位置,重寫onTouchEvent()來相應(yīng)事件.這里我們定義一個(gè)類似系統(tǒng)ScrollView的效果,首先,我們測(cè)量子View

   @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);
        }
    }

接下來我們要對(duì)子View的位置計(jì)算,讓每一個(gè)View放置的時(shí)候都是全屏,這樣我們就滑動(dòng),我們這樣來設(shè)置ViewGroup的高度

    @Override
    protected void onLayout(boolean changed,
                            int l, int t, int r, int b) {
        int childCount = getChildCount();
        // 設(shè)置ViewGroup的高度
        MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
        mlp.height = mScreenHeight * childCount;
        setLayoutParams(mlp);
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                child.layout(l, i * mScreenHeight,
                        r, (i + 1) * mScreenHeight);
            }
        }
    }

響應(yīng)事件、滑動(dòng)事件

    @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;
                }
                if (getScrollY() > getHeight() - mScreenHeight) {
                    dy = 0;
                }
                scrollBy(0, dy);
                mLastY = y;
                break;
            case MotionEvent.ACTION_UP:
                mEnd = getScrollY();
                int dScrollY = mEnd - mStart;
                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;
    }

最后~

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

3.8 事件攔截機(jī)制分析

image.png

1.點(diǎn)擊View:

image.png

2.ViewGroupA覺得任務(wù)很簡(jiǎn)單,自己處理就可以。在onInterceptTouchEvent()返回true

image.png

3.ViewGroupA不想做這個(gè)任務(wù),ViewGroupB想做。

image.png

4.View罷工了,不想做任務(wù)了,也不用向上級(jí)報(bào)告任務(wù),onTouchEvent直接返回true

image.png

5.View不返回true了,ViewGroupB的onTouchEvent也返回 true

image.png

大功告成?。?!下班!??!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 【Android 自定義View】 [TOC] 自定義View基礎(chǔ) 接觸到一個(gè)類,你不太了解他,如果貿(mào)然翻閱源碼只...
    Rtia閱讀 4,141評(píng)論 1 14
  • 6、View的繪制 (1)當(dāng)測(cè)量好一個(gè)View之后,我們就可以簡(jiǎn)單的重寫 onDraw()方法,并在 Canvas...
    b5e7a6386c84閱讀 1,980評(píng)論 0 3
  • Android控件架構(gòu)與自定義控件(一) (本文并非原創(chuàng)文章,整理摘抄方便自己查看,原文地址為Android控件架...
    b5e7a6386c84閱讀 1,046評(píng)論 0 6
  • 不少人都有鼻炎,也有一部分人根本不知鼻炎為何物。 秉著人道主義精神,也秉著給廣大不知鼻炎為何物的看官們科普一下的心...
    十七修閱讀 1,259評(píng)論 0 3
  • 這兩天左肩真的就不疼了,真好,果然是虛驚一場(chǎng)??墒亲罱鼮槭裁床豢先胨??一個(gè)黃金小長(zhǎng)假,什么都沒做,所以怎么樣都不...
    阿雪_Michelle閱讀 259評(píng)論 0 1

友情鏈接更多精彩內(nèi)容