天天看點

Android群英傳學習——第三章、Android控件架構與自定義控件詳解一、Android控件架構二、View的測量三、View的繪制四、ViewGroup的測量與繪制五、自定義View下面放上完整的效果和代碼:下面放上完整的代碼下面直接看完整代碼:五、事件攔截機制分析

自定義控件我有跟着幾個大神做的成果做一些,但是一直沒有仔細的完整的學一遍理論,是以這章要好好學一下。

這一章的内容如下:

——Android控件架構

——View的測量與繪制

——ViewGroup的測量與繪制

——自定義控件的三種方式

——事件的攔截機制

一、Android控件架構

如下圖展示了View視圖樹,在控制樹的頂部是一個ViewParent對象,是整個樹的控制核心,所有互動管理事件都由它來統一排程和配置設定。ViewGroup控件作為父控件可以包含多個View控件,并管理其包含的View控件。通常在Activity中使用的findViewById()方法,就是在控制樹中以樹的深度優先周遊來查找對應元素。

Android群英傳學習——第三章、Android控件架構與自定義控件詳解一、Android控件架構二、View的測量三、View的繪制四、ViewGroup的測量與繪制五、自定義View下面放上完整的效果和代碼:下面放上完整的代碼下面直接看完整代碼:五、事件攔截機制分析

每個Activity都包含一個Window對象,Android中通常用PhoneWindow來實作。PhoneWindow将一個DecorView設定為整個應用視窗的根View,它封裝了一些視窗操作的通用方法,在顯示上,它将螢幕分為兩部分:TitleView和ContentView。如下圖:

Android群英傳學習——第三章、Android控件架構與自定義控件詳解一、Android控件架構二、View的測量三、View的繪制四、ViewGroup的測量與繪制五、自定義View下面放上完整的效果和代碼:下面放上完整的代碼下面直接看完整代碼:五、事件攔截機制分析

setContentView()即加載ContentView部分的布局。如果使用者通過設定requestWindowFeature(Window.FEATURE_NO_TITLE)來設定全屏顯示,就一定要放在setContentView()方法之前才能生效。

在代碼中,當程式在onCreate()方法中調用setContentView()方法後,ActivityManagerService會回調onResume()方法,此時系統才會把整個DecorView添加到PhoneWindow中,并讓其顯示出來,進而最終完成界面繪制。

二、View的測量

Android系統提供了一個強大的類——MeasureSpec類來幫助我們測量View。它是一個32位的int值,高2位為測量的模式,低30位為測量的大小。測量的模式可以為以下三種

EXACTLY——精确值模式:當控件的寬高屬性指定為具體數值時使用
AT_MOST——最大值模式:當控件的寬高屬性指定為自适應時使用
UNSPECIFIED——不精确模式:它不指定其大小測量模式,通常在繪制自定義View時才使用。
           

在自定義控件時,若指定了控件具體寬高值或者是match_parent屬性,則使用EXACTLY模式,使用指定的specSize即可;如果指定的是wrap_content屬性,即AT_MOST模式,必須重寫onMeasure()方法來指定大小,通常作法是取出我們指定的大小與specSize中最小的一個來作為最後的測量值。代碼如下:

public class MainActivity extends View {
    public MainActivity(Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
    }

    private int measureHeight(int heightMeasureSpec) {
        int result = ;
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);

        if (specMode == MeasureSpec.EXACTLY){
            result = specSize;
        }else{
            result = ;
            if(specMode == MeasureSpec.AT_MOST){
                result = Math.min(result,result);
            }
        }
        return result;
    }

    private int measureWidth(int widthMeasureSpec) {
        int result = ;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);

        if (specMode == MeasureSpec.EXACTLY){
            result = specSize;
        }else{
            result = ;
            if(specMode == MeasureSpec.AT_MOST){
                result = Math.min(result,result);
            }
        }
        return result;
    }


}
           

三、View的繪制

測量好一個View後,通常需要通過繼承View并重寫它的onDraw()方法來完成繪圖。onDraw()中有一個參數,就是Canvas對象,這個對象就像建立了一個畫闆,使用Paint就可以在上面作畫了。建立Cavas對象代碼:

Canvas canvas = new Canvas(bitmap);
           

傳進去的bitmap與Canvas畫布緊緊聯系,這個過程稱為裝載畫布,這個bitmap用來存儲繪制在Canvas上的像素資訊,調用所有的Canvas.drawXXX方法都發生在這個bitmap上。

canvas.drawBitmap(bitmap1,0,0,null);
           
//繪制直線
canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);

//繪制矩形
canvas.drawRect(float left, float top, float right, float bottom, Paint paint);

//繪制圓形
canvas.drawCircle(float cx, float cy, float radius, Paint paint);

//繪制字元
canvas.drawText(String text, float x, float y, Paint paint);

//繪制圖形
canvas.drawBirmap(Bitmap bitmap, float left, float top, Paint paint);
           

四、ViewGroup的測量與繪制

ViewGroup會管理其子View,其中一個管理項目就是負責子View的顯示大小。當ViewGroup指定為wrap_content時,ViewGroup會對子View周遊,從子View的大小來确定自己的大小。如果ViewGroup有指定的值則按值設定自己的大小。

子View測量完後,ViewGroup會周遊子View的Layout方法來指定其具體顯示的位置,進而決定其布局位置。

自定義ViewGroup時,通常會重寫onLayout()方法來控制其子View顯示位置的邏輯。

ViewGroup通常不需繪制。但是它會使用dispatchDraw()方法來繪制其子View,其過程是通過周遊是以子View,并調用子View的繪制方法來完成繪制工作。

五、自定義View

心心念念想學的一部分。。它的作用就不用多說了,直接快點開始學把~

在View中比較重要的回調方法

1、onFinishInflate()

//從XML加載元件後回調
    @Override
    protected void onFinishInflate() {
        // TODO Auto-generated method stub
        super.onFinishInflate();
    }
           

2、onSizeChanged()

//元件大小改變時回調
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // TODO Auto-generated method stub
        super.onSizeChanged(w, h, oldw, oldh);
    }
           

3、onMeasure()

// 回調該方法進行測量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
           

4、onLayout()

// 回調該方法來确定顯示的位置
    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        // TODO Auto-generated method stub
        super.onLayout(changed, left, top, right, bottom);
    }
           

5、onTouchEvent()

// 監聽到觸摸時間時回調
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        return super.onTouchEvent(event);
    }
           

6、onDraw()

// 繪圖
    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
    }
           

建立自定義View的時候,并不需要重寫所有的方法。通常有一下三種方法來實作自定義的控件:

對現有控件進行拓展
通過組合來實作新的控件
重寫View來實作全新的控件
           

1、對現有控件進行拓展

這種方法就是在原生控件的基礎上進行拓展,增加新的功能、修改顯示的UI等。

第一個例子:讓TextView的背景更加豐富

Android群英傳學習——第三章、Android控件架構與自定義控件詳解一、Android控件架構二、View的測量三、View的繪制四、ViewGroup的測量與繪制五、自定義View下面放上完整的效果和代碼:下面放上完整的代碼下面直接看完整代碼:五、事件攔截機制分析

TextView使用onDraw()方法繪制要顯示的文字,繼承了系統的TextView後,一定要重寫其onDraw()方法才能修改TextView展示的 效果。其實程式調用super.onDraw()方法來實作原生控件的功能,但是在調用super.onDraw()方法前和之後,我們可以實作自己的邏輯,即在系統繪制文字前後,完成自己的操作。如下所示:

@Override
    protected void onDraw(Canvas canvas) {
        //在回調父類之前,實作自己的邏輯,對textview來說就是繪制文本内容前
        super.onDraw(canvas);
         //在回調父類之後,實作自己的邏輯,對textview來說就是繪制文本内容後
     }
           

然後我們分析要實作這樣的效果,需要繪制兩個不同大小的矩形,形成一個重疊效果。那我們就要先初始化兩個畫筆:

//執行個體化畫筆
        paint1 = new Paint();
        //設定顔色
        paint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
        //設定style
        paint1.setStyle(Paint.Style.FILL);

        //執行個體化另一個畫筆
        paint2 = new Paint();
        paint2.setColor(Color.YELLOW);
        paint2.setStyle(Paint.Style.FILL);
           

然後開始繪制

//繪制外層
        canvas.drawRect(, , getMeasuredWidth(), getMeasuredHeight(), paint1);
        //繪制内層
        canvas.drawRect(, , getMeasuredWidth() - , getMeasuredHeight() - , paint2);

        canvas.save();
        //繪制文字前平移10像素
        canvas.translate(, );
        //父類完成方法
        super.onDraw(canvas);
        canvas.restore();
           

第二個例子:實作一個動态的文字閃動效果

Android群英傳學習——第三章、Android控件架構與自定義控件詳解一、Android控件架構二、View的測量三、View的繪制四、ViewGroup的測量與繪制五、自定義View下面放上完整的效果和代碼:下面放上完整的代碼下面直接看完整代碼:五、事件攔截機制分析

想要實作這一效果,可以充分利用Android中Paint對象的Shader渲染器。通過設定一個不斷變化的LinearGradient,并使用帶有該屬性的Paint對象來繪制要顯示的文字。首先我們要在onSizeChanged()方法中完成一些初始化操作。

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mViewWidth == ) {
            mViewWidth = getMeasuredWidth();
            if (mViewWidth > ) {
                //擷取畫筆對象
                mPaint = getPaint();
                //渲染器
                mLinearGradient = new LinearGradient(, , mViewWidth, , new int[]{Color.BLUE, , Color.BLUE},
                        null, Shader.TileMode.CLAMP);
                mPaint.setShader(mLinearGradient);
                //矩陣
                matrix = new Matrix();
            }
        }
    }
           

其中最關鍵的就是使用getPaint()方法擷取目前繪制TextView的Paint對象,并給這個Paint對象設定原聲TextView沒有的LinearGradient屬性。最後在onDraw()方法中,通過矩陣的方法來不斷平移漸變效果,進而産生動态閃動效果:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (matrix != null) {
            mTranslate += mViewWidth + ;
            if (mTranslate >  * mViewWidth / ) {
                mTranslate = -mViewWidth;
            }
            matrix.setTranslate(mTranslate, );
            mLinearGradient.setLocalMatrix(matrix);
            //每隔100毫秒閃動一下
            postInvalidateDelayed();
        }
    }
}
           

下面放上完整的效果和代碼:

Android群英傳學習——第三章、Android控件架構與自定義控件詳解一、Android控件架構二、View的測量三、View的繪制四、ViewGroup的測量與繪制五、自定義View下面放上完整的效果和代碼:下面放上完整的代碼下面直接看完整代碼:五、事件攔截機制分析

布局代碼

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.administrator.myview.TextViewTestOne
        android:id="@+id/tv_01"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:text="Android"
        android:textSize="25sp"/>

    <com.example.administrator.myview.TextViewTestTwo
        android:id="@+id/tv_02"
        android:layout_marginTop="30dp"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:text="Android"
        android:textSize="25sp"/>
           

TextViewTestOne.java

public class TextViewTestOne extends TextView {
    private Paint paint1, paint2;

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

    private void init() {
        //執行個體化畫筆1
        paint1 = new Paint();
        //設定顔色
        paint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
        //設定style
        paint1.setStyle(Paint.Style.FILL);

        //同上
        paint2 = new Paint();
        paint2.setColor(Color.YELLOW);
        paint2.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //繪制外層
        canvas.drawRect(, , getMeasuredWidth(), getMeasuredHeight(), paint1);
        //繪制内層
        canvas.drawRect(, , getMeasuredWidth() - , getMeasuredHeight() - , paint2);

        canvas.save();

        //繪制文字前平移10像素
        canvas.translate(, );
        //父類完成方法
        super.onDraw(canvas);

        canvas.restore();
    }

}
           

TextViewTestTwo.java

public class TextViewTestTwo extends TextView {
    private LinearGradient mLinearGradient;
    private Matrix mGradientMatrix;
    private Paint mPaint;
    int mTranslate;
    int mViewWidth;

    public TextViewTestTwo(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if(mViewWidth == ){
            mViewWidth = getMeasuredWidth();
            if(mViewWidth>){
                mPaint = getPaint();
                mLinearGradient = new LinearGradient(,,mViewWidth,,
                        new int[]{
                                Color.BLUE,,Color.BLUE},null,Shader.TileMode.CLAMP
                        );
                mPaint.setShader(mLinearGradient);
                mGradientMatrix = new Matrix();
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mGradientMatrix!=null){
            mTranslate += mViewWidth/;
            if(mTranslate>*mViewWidth){
                mTranslate = -mViewWidth;
            }
            mGradientMatrix.setTranslate(mTranslate,);
            mLinearGradient.setLocalMatrix(mGradientMatrix);
            postInvalidateDelayed();
        }
    }
}
           

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextViewTestOne tv_01 = (TextViewTestOne) findViewById(R.id.tv_01);
        TextViewTestTwo tv_02 = (TextViewTestTwo) findViewById(R.id.tv_02);

    }
}
           

2、建立複合控件

這種方式通常需要繼承一個合适的ViewGroup,再給它添加指定功能的控件,進而組合成新的複合控件。通過這種方式建立的控件,我們一般會給他指定一些可配置的屬性,使其具有更強的拓展性。以下是一個TopBar示例。

Android群英傳學習——第三章、Android控件架構與自定義控件詳解一、Android控件架構二、View的測量三、View的繪制四、ViewGroup的測量與繪制五、自定義View下面放上完整的效果和代碼:下面放上完整的代碼下面直接看完整代碼:五、事件攔截機制分析

1)定義屬性

在res資源目錄下建立一個attrs.xml的屬性定義檔案,然後通過如下代碼定義相應的屬性即可。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <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>
</resources>
           

确定号屬性後,就可以建立一個自定義控件TopBar,并讓他繼承ViewGroup,為了簡單這裡繼承了RelativeLayout。在構造方法中通過如下代碼來擷取在xml布局檔案中自定義的屬性:

系統提供了TypedArray這樣的資料機構來擷取自定義屬性集,通過它的對象的getString(),getColor()等方法,就可以擷取這些定義的屬性值,代碼如下:

//通過這個方法,你可以從你的attrs.xml檔案下讀取讀取到的值存儲在你的TypedArray
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);

        //讀取出相應的值設定屬性
        mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor, );
        mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftBackground);
        mLeftText = ta.getString(R.styleable.TopBar_leftText);

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

        mTitleSize = ta.getDimension(R.styleable.TopBar_titleTextSize, );
        mTitleColor = ta.getColor(R.styleable.TopBar_titleTextColor, );
        mTitle = ta.getString(R.styleable.TopBar_title);

        //擷取完TypedArray的值之後,一般要調用recycle方法來避免重複建立時候的錯誤
        ta.recycle();
           

2)組合控件

接下來,就要開始組合基礎控件。建立左邊的按鈕,中間的标題欄和右邊的按鈕,設定他們的一些屬性,然後設定各個控件在ViewGroup中的布局,最後用addView()方法将這三個控件加入到定義的TopBar模闆中。

mLeftButton = new Button(context);

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


        //為建立的元素指派
        mLeftButton.setTextColor(mLeftTextColor);
        mLeftButton.setBackground(mLeftBackground);
        mLeftButton.setText(mLeftText);

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

        mTitleView.setText(mTitle);
        mTitleView.setTextColor(mTitleColor);
        mTitleView.setTextSize(mTitleSize);
        mTitleView.setGravity(Gravity.CENTER);


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


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

        mTitleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
        mTitleParams.addRule(RelativeLayout.CENTER_IN_PARENT,TRUE);
        addView(mTitleView,mTitleParams);
           
定義接口——接下來建立一個接口,實作按鈕的點選回調
//定義接口
//接口對象,實作回調機制,在回調方法中
//通過映射的接口對象調用接口中的方法
//而不用去考慮如何實作,具體的實作由調用者去建立
public interface topbarClickListener {
    //左按鈕點選事件
    void leftClick();
    //右按鈕點選事件
    void rightClick();
}
           
暴露接口給調用者——在模闆方法中,為左右按鈕增加點選事件
//按鈕的點選事件,不需要具體的實作
        //隻需調用接口的方法,回調的時候,會有具體的實作
        mLeftButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mListener.leftClick();
            }
        });
 mRightButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mListener.rightClick();
            }
        });


  //暴露一個方法給調用者來注冊接口回調
    //通過接口來獲得回調者對接口方法的實作
    public void setOnTopbarClickListener(topbarClickListener mListener){
        this.mListener = mListener;
    }
           
實作接口回調——在調用這的代碼中,要實作這樣一個接口,并完成接口中的方法
mTopbar = (TopBar) findViewById(R.id.mTopbar);

        mTopbar.setOnTopbarClickListener(new topbarClickListener() {
            @Override
            public void leftClick() {
                Toast.makeText(MainActivity.this, "點選左側按鈕", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void rightClick() {
                Toast.makeText(MainActivity.this, "點選右側按鈕", Toast.LENGTH_SHORT).show();

            }
        });
           

為了簡單,隻顯示了兩個Toast來區分不同按鈕的點選事件

3)引用UI模闆

最後一步,自然是在需要使用的地方引用UI模闆,在引用前,需要指定引用第三方控件的名字空間。

例如

這行代碼就是在指定引用的名字空間xmlns,即xml namespace。這裡指定了名字空間為“android”,是以在接下來使用系統屬性時,才可以使用“android:”來引用Android的系統屬性。

如果使用自定義屬性,就要建立自己的名字空間:

<?xml version="1.0" encoding="utf-8"?>
<com.example.administrator.topbartest.TopBar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    custom:leftBackground="#128"
    custom:leftText="Back"
    custom:leftTextColor="#FFFFFF"

    custom:rightBackground="#128"
    custom:rightText="More"
    custom:rightTextColor="#FFFFFF"

    custom:title="自定義标題"
    custom:titleTextColor="#000"
    custom:titleTextSize="15sp">

</com.example.administrator.topbartest.TopBar>
           

通過如上所示代碼,我們就可以在其他的布局中,直接通過标簽來引用這個UI模闆View。

下面放上完整的代碼

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--聲明使用自定義屬性,通過name屬性來确定引用的名字-->
    <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>
</resources>
           

topbarClickListener.java

//定義接口
//接口對象,實作回調機制,在回調方法中
//通過映射的接口對象調用接口中的方法
//而不用去考慮如何實作,具體的實作由調用者去建立
public interface topbarClickListener {
    //左按鈕點選事件
    void leftClick();
    //右按鈕點選事件
    void rightClick();
}
           

TopBar.java

public class TopBar  extends RelativeLayout  {

    private int mLeftTextColor;
    private Drawable mLeftBackground;
    private String mLeftText;

    private int mRightTextColor;
    private Drawable mRightBackgroup;
    private String mRightText;

    private float mTitleSize;
    private int mTitleColor;

    private String mTitle;

    private Button mLeftButton,mRightButton;
    private TextView mTitleView;
    private LayoutParams mLeftParams,mRightParams,mTitleParams;

    private topbarClickListener mListener;

    //帶參構造方法
    public TopBar(Context context, AttributeSet attrs) {
        super(context, attrs);

        //通過這個方法,你可以從你的attrs.xml檔案下讀取讀取到的值存儲在你的TypedArray
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);

        //讀取出相應的值設定屬性
        mLeftTextColor = ta.getColor(R.styleable.TopBar_leftTextColor, );
        mLeftBackground = ta.getDrawable(R.styleable.TopBar_leftBackground);
        mLeftText = ta.getString(R.styleable.TopBar_leftText);

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

        mTitleSize = ta.getDimension(R.styleable.TopBar_titleTextSize, );
        mTitleColor = ta.getColor(R.styleable.TopBar_titleTextColor, );
        mTitle = ta.getString(R.styleable.TopBar_title);

        //擷取完TypedArray的值之後,一般要調用recycle方法來避免重複建立時候的錯誤
        ta.recycle();

        mLeftButton = new Button(context);
        //按鈕的點選事件,不需要具體的實作
        //隻需調用接口的方法,回調的時候,會有具體的實作
        mLeftButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mListener.leftClick();
            }
        });


        mRightButton = new Button(context);
        mRightButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mListener.rightClick();
            }
        });


        mTitleView = new TextView(context);


        //為建立的元素指派
        mLeftButton.setTextColor(mLeftTextColor);
        mLeftButton.setBackground(mLeftBackground);
        mLeftButton.setText(mLeftText);

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

        mTitleView.setText(mTitle);
        mTitleView.setTextColor(mTitleColor);
        mTitleView.setTextSize(mTitleSize);
        mTitleView.setGravity(Gravity.CENTER);


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


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

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


    //暴露一個方法給調用者來注冊接口回調
    //通過接口來獲得回調者對接口方法的實作
    public void setOnTopbarClickListener(topbarClickListener mListener){
        this.mListener = mListener;
    }
}
           

topbar.xml

<?xml version="1.0" encoding="utf-8"?>
<com.example.administrator.topbartest.TopBar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    custom:leftBackground="#128"
    custom:leftText="Back"
    custom:leftTextColor="#FFFFFF"

    custom:rightBackground="#128"
    custom:rightText="More"
    custom:rightTextColor="#FFFFFF"

    custom:title="自定義标題"
    custom:titleTextColor="#000"
    custom:titleTextSize="15sp">

</com.example.administrator.topbartest.TopBar>
           

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include
        layout="@layout/topbar"
        android:id="@+id/mTopbar"/>

</LinearLayout>
           

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private TopBar mTopbar;

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


        mTopbar = (TopBar) findViewById(R.id.mTopbar);

        mTopbar.setOnTopbarClickListener(new topbarClickListener() {
            @Override
            public void leftClick() {
                Toast.makeText(MainActivity.this, "點選左側按鈕", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void rightClick() {
                Toast.makeText(MainActivity.this, "點選右側按鈕", Toast.LENGTH_SHORT).show();

            }
        });
    }
}
           

3、重寫View來實作全新的控件

例子一:比例圖

Android群英傳學習——第三章、Android控件架構與自定義控件詳解一、Android控件架構二、View的測量三、View的繪制四、ViewGroup的測量與繪制五、自定義View下面放上完整的效果和代碼:下面放上完整的代碼下面直接看完整代碼:五、事件攔截機制分析

我們來分析一下這個圖是怎麼畫出來的。首先這個自定義View可以分為三部分,分别是中間的圓形,中間顯示的文字和外圈的弧線。

下面直接看完整代碼:

CircleView.java

public class CircleView extends View {
    //圓的長度
    private int mCircleXY;
    //螢幕高寬
    private int w,h;
    //圓的半徑
    private float mRadius;
    //圓的畫筆
    private Paint mCirclePaint;
    //弧線的畫筆
    private Paint mArcPaint;
    //文本畫筆
    private Paint mTextPaint;
    //需要顯示的文字
    private String mShowText = "hahahaha";
    //文字大小
    private int mTextSize = ;
    //圓心掃描的弧度
    private int mSweepAngle = ;

    public CircleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //擷取螢幕高寬
        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        w = wm.getDefaultDisplay().getWidth();
        h = wm.getDefaultDisplay().getHeight();

        init();
    }

    private void init() {
        mCircleXY = w/;
        mRadius = (float)(w *  / );

        mCirclePaint = new Paint();
        mCirclePaint.setColor(Color.BLUE);

        mArcPaint = new Paint();
        //設定線寬
        mArcPaint.setStrokeWidth();
        //設定空心
        mArcPaint.setStyle(Paint.Style.STROKE);
        //設定顔色
        mArcPaint.setColor(Color.BLUE);

        mTextPaint = new Paint();
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setTextSize(mTextSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //繪制矩形
        RectF mArcRectF = new RectF((float)(w * ),(float)(w * ),(float)(w * ),(float)(w * ));
        //繪制圓
        canvas.drawCircle(mCircleXY,mCircleXY,mRadius,mCirclePaint);
        //繪制弧線
        canvas.drawArc(mArcRectF,,mSweepAngle,false,mArcPaint);
        //繪制文本
        canvas.drawText(mShowText,,mShowText.length(),mCircleXY-mTextSize *  ,mCircleXY+mTextSize/,mTextPaint);

    }

    //設定一個對外的弧度方法
    public void setSweepValues(int sweepValues){
        if(sweepValues != ){
            mSweepAngle = sweepValues;
        }else {
            //如果沒有,我們預設設定
            mSweepAngle = ;
        }
        //重新整理
        invalidate();
    }
}
           

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.administrator.circledemo.CircleView
        android:id="@+id/cv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
           

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

    CircleView circleView = (CircleView) findViewById(R.id.cv);
    circleView.setSweepValues(180);
}
           

}

public class MainActivity extends AppCompatActivity {


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

        CircleView circleView = (CircleView) findViewById(R.id.cv);
        circleView.setSweepValues();
    }
}
           

例子二、音頻條形圖

Android群英傳學習——第三章、Android控件架構與自定義控件詳解一、Android控件架構二、View的測量三、View的繪制四、ViewGroup的測量與繪制五、自定義View下面放上完整的效果和代碼:下面放上完整的代碼下面直接看完整代碼:五、事件攔截機制分析

直接看代碼吧

YinPinRectView.java

public class YinPinRectView extends View {
    private Paint mPaint;
    //定義矩形數量
    int mRectCount = ;
    //定義矩形寬度和高度
    private  int mRectWidth ,mRectHeight;
    //設定偏移量
    private  int offset = ;
    //定義目前矩形高度
    private float currentHeight;
    //設定一個随機數
    private double mRandom;
    private LinearGradient mLinearGradient;
    //螢幕寬高
    private int w;

    public YinPinRectView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        w = wm.getDefaultDisplay().getWidth();

        init();
    }

    private void init() {
        mPaint = new Paint();
        //設定線寬
        mPaint.setStrokeWidth();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        for(int i = ;i<mRectCount;i++){
            currentHeight = getRectHeight();
            canvas.drawRect((float)(w *  + mRectWidth * i + offset),currentHeight,
                    (float)(w *  + mRectWidth * (i+) ),mRectHeight,mPaint);
        }
        postInvalidateDelayed();
    }

    //随即改變矩形的高
    private float getRectHeight() {
        mRandom = Math.random();
        float currentHeight = (float) (mRectHeight * mRandom);
        return currentHeight;
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        w = getWidth();
        mRectHeight = getHeight();
        mRectWidth = (int)(w /mRectCount);//計算矩形的寬
        //漸變效果
        mLinearGradient = new LinearGradient(,,mRectWidth,mRectCount,Color.YELLOW,Color.BLUE, Shader.TileMode.CLAMP);
        mPaint.setShader(mLinearGradient);
    }
}
           

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.administrator.yinpinrectdemo.YinPinRectView
        android:id="@+id/yp"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
           

MainActivity.java

public class MainActivity extends AppCompatActivity {


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

        YinPinRectView yp = (YinPinRectView) findViewById(R.id.yp);
    }
}
           

五、事件攔截機制分析

http://blog.csdn.net/chunqiuwei/article/details/41084921

繼續閱讀