Android 自定義控件 詳解

導讀:

  • 不管是剛學習安卓的同學,還是在工作的小夥伴,自定義控件這個點都是非常重要的
  • 雖然安卓本身已經有一部分做好的控件,但是實現的功能以及顯示效果也是很有限的,那麼自定義控件就是我們最好的選擇
  • 下文是本人學習中總結的內容分享給大家..0.0

本文將以一個自定義組合控件作爲例子

配合該老師講解的視頻食用更佳:
http://www.imooc.com/course/programdetail/pid/24

ps: 如果有什麼理解錯誤,歡迎評論指正修改

效果圖:

點擊 “Back” 或 “More” 彈出吐司,並且中間的自定義標題也會變成點擊的內容

page1

page2

page3

實現方式:

一般實現自定義控件會有三種方式:

  • 1.繼承已有的控件實現 (自定義控件)

  • 2.組合已有的控件實現 (自定義組合控件)

  • 3.完全自定義控件(自定義View)


實現步驟:


一. 在res/Values目錄下創建attrs.xml文件,設計需要的屬性

屬性 作用
reference 某一資源ID
color 顏色值
boolean 布爾值
dimension 尺寸值
float 浮點值
integer 整型值
string 字符串
fraction 百分數
enum 枚舉值 (多值選一)
flag 位或運算 (多值組合)
<!--自定義屬性名(自定義就好)-->

    <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="leftText" format="string"/>
        <attr name="leftBackground" format="reference|color"/>
        <attr name="rightTextColor" format="color"/>
        <attr name="rightText" format="string"/>
        <!--平常在定義 Background 屬性,不僅能通過字節碼調用顏色,還能@調用資源文件-->
        <attr name="rightBackground" format="reference|color"/>
    </declare-styleable>

注意:

1.定義一個Background(Drawable屬性),設置要 format="reference|color"  因爲我們平常在定義Background 不僅能通過字節碼調用顏色,還能@調用資源文件

2.attrs.xml 設置好屬性以後,系統會在R.styleable 文件中生成 類似 R.styleable.Topbar_leftTextColor 的值,用於 TypedArray 獲取attr便籤定義的屬性

3.enum 或flag 標籤
  <attr name="postion" value="enum">
        <enum name="left" value="0"/>  
        <enum name="right" value="1"/> 
  </attr>

調用:
   typeArray.getInteger(R.styleable_XX,0); 得到對應值的屬性


二. 實現一個自定義的”View”(如 自定義類 繼承 RelativeLayout)

2.1.實現”View”的構造函數

四個構造函數(一般關注前兩個即可) 作用
“view”(Context context) 常規構造函數(代碼中實例化)
“view”(Context context,AttributeSet attrs) 用於在XML中使用,可以指定自定義屬性
“view”(Context context, AttributeSet attrs, int defStyleAttr) 用於在XML中使用,可以指定自定義屬性,並指定樣式
“view”(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) 用於在XML中使用,可以指定自定義屬性,並指定樣式及其資源
   /**
     * 其他類通過new TopBar() 時被調用
     *
     * @return
     */
    public TopBar(Context context) {
        super(context);

    }

    /**
     * 實現父類View的構造函數
     *
     * @param context
     * @param attrs
     */
    public TopBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        initData(context, attrs);
        //initView(context);
        initView();
        initEvent();
    }

2.2.獲取 attrs.xml 自定義的屬性

    /**
     * 
     *
     * @param context 上下文
     * @param attrs   TopBar(Context context, AttributeSet attrs) 由構造函數傳過來
     */
    private void initData(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Topbar);

        //左側按鈕數據
        leftTextColor = typedArray.getColor(R.styleable.Topbar_leftTextColor, 0);
        leftBackground = typedArray.getDrawable(R.styleable.Topbar_leftBackground);
        leftText = typedArray.getString(R.styleable.Topbar_leftText);

        //右側按鈕數據
        rightTextColor = typedArray.getColor(R.styleable.Topbar_rightTextColor, 0);
        rightBackground = typedArray.getDrawable(R.styleable.Topbar_rightBackground);
        rightText = typedArray.getString(R.styleable.Topbar_rightText);

        //自定義標題數據
        titleTextSize = typedArray.getDimension(R.styleable.Topbar_titleTextSize, 0);
        title = typedArray.getString(R.styleable.Topbar_title);
        titleTextColor = typedArray.getColor(R.styleable.Topbar_titleTextColor, 0);

        //回收,避免浪費資源以及由於避免緩存報的錯誤
        typedArray.recycle();

        }

2.3.設置控件之間的佈局關係(看需求,使用1或2都可以)

  1. 用代碼構建自定義控件佈局
        //addRule() 是 RelativeLayout 特有的Api

        //左側Button佈局
        leftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams
                .WRAP_CONTENT);
        leftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);//居左對齊
        addView(leftButton, leftParams);//將leftButton添加到我們的RelativeLayout 中

        //右側Button佈局
        rightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams
                .WRAP_CONTENT);
        rightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);//居右對齊
        addView(rightButton, rightParams);//將rightButton添加到我們的RelativeLayout 中

        //自定義標題佈局
        titleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams
                .MATCH_PARENT);
        titleParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);//居中對齊

        addView(tvTitle, titleParams);//將tvTitle添加到我們的RelativeLayout 中
  2. 用打氣筒xml文件構建自定義控件佈局 
       //佈局文件,根佈局對象,是否作爲根佈局
        View rootView = View.inflate(getContext(),R.layout.custom_controls, this);

        leftButton = (Button) rootView.findViewById(R.id.btn_left);
        rightButton = (Button) rootView.findViewById(R.id.btn_right);
        tvTitle = (TextView) rootView.findViewById(R.id.tv_title);

2.4.將自定義屬性和View綁定並顯示出來

         leftButton = new Button(context);
        rightButton = new Button(context);
        tvTitle = new TextView(context);

        //左側Button
        leftButton.setTextColor(leftTextColor);
        leftButton.setBackground(leftBackground);
        leftButton.setText(leftText);

        //右側Button
        rightButton.setTextColor(rightTextColor);
        rightButton.setBackground(rightBackground);
        rightButton.setText(rightText);

        //自定義標題
        tvTitle.setTextColor(titleTextColor);
        tvTitle.setText(title);
        tvTitle.setTextSize(titleTextSize);
        tvTitle.setGravity(Gravity.CENTER); //居中顯示文本


注意:



1.使用TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Topbar); 獲取屬性

記得  typedArray.recycle();  避免浪費資源以及由於避免緩存報的錯誤  

2.獲取屬性值函數: obtainStyledAttributes

public final TypedArray obtainStyledAttributes(
            AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
            @StyleRes int defStyleRes) {
        return getTheme().obtainStyledAttributes(
            set, attrs, defStyleAttr, defStyleRes);
    } 

  四個參數的意思分別是:

    set:屬性值的集合,一般爲第二個構造函數中的attrs

    attrs:我們要獲取的屬性的資源ID的一個數組

    defStyleAttr:這個是當前Theme中的一個attribute,是指向style的一個引用,當在layout xml中和style中都沒有爲View指定屬性時,會從Theme中這個attribute指向的Style中查找相應的屬性值,如果這個參數傳入0表示不向Theme中搜索默認值

    defStyleRes:這個也是指向一個Style的資源ID,但是僅在defStyleAttr爲0或defStyleAttr不爲0但Theme中沒有爲defStyleAttr屬性賦值時起作用

函數返回值: 
 TypedArray 是一個類,包含要獲取值的一個集合 

3.舊版獲取屬性(通過命名空間,屬性名獲取):

 Android Studio: String title = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "title");
 eclipse:     String title = attrs.getAttributeValue("http://schems.android.com/apk/res/(自定義" View " 的工程包名)", "title");

4.優先級問題
在xml佈局文件指定屬性,在style文件指定屬性,在Theme文件指定屬性存在優先級;
-->
在XML中直接指定>在style中指定值>在Activity theme中指定了defStyleAttr(>0)>在Application theme中指定了defStyleAttr(>0)>指定了defStyleRes(>0)>在Activity theme中指定的值>在Application theme中指定的值



三. 在佈局文件中引用我們的自定義控件

  1. 在根佈局定義定義xmlns(命名空間) 如:xmlns:app(自定義)=”http://schems.android.com/apk/res-auto”
  2. 引用我們的自定義控件包名

<RelativeLayout
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="zs.xmx.customcontrols.MainActivity">

    <zs.xmx.customcontrols.TopBar
        android:id="@+id/topbar"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        custom:leftBackground="#000000"
        custom:leftText="Back"
        custom:leftTextColor="#FF0000"
        custom:rightBackground="#000000"
        custom:rightText="More"
        custom:rightTextColor="#FFFFFF"
        custom:title="自定義標題"
        custom:titleTextColor="#123412"
        custom:titleTextSize="10sp"
        />
</RelativeLayout>

注意:

1. xmlns(xml命名空間) 主要用來調用xml文件
2. 在Android Studio:xmlns:app(自定義)="http://schems.android.com/apk/res-auto"
3. 在eclipse 中: xmlns:app(自定義)="http://schems.android.com/apk/res/(自定義" View " 的工程包名)"

四. 動態的控制自定義控件

兩種方式:

  1. 接口回調機制實現
  2. 在自定義的 “View” 中,寫一個方法,讓外部類調用

    • 接口回調機制方法實現
      1. 定義一個接口,將我們要實現的方法定義
public interface topbarClickListener{
    public void leftClick();
    public void rightClick();
}

2.定義我們的接口作爲成員變量

  private topbarClickListenr listener; 
  1. 定義一個方法暴露給調用者,類似button.setOnClickListen(),這樣就可以將我們接口裏定義的方法以匿名內部類的形式傳遞進來
  public void setOnTopbarClickListenr(topbarClickListener listener){
       this.listener=listener;
  }

4.在我們原來要操作的邏輯方法改成接口裏定義的方法,讓外部類實現

//例子:
   button.setOnClickListener(new OnClickListener()){

    @Override 

    public void onClick(View v){
       listener.leftClick();
    }
 }
  • 在自定義的 “View” 中,寫一個方法,讓外部類調用
// 例子:(外部類 find 到這個控件,用 . 的方式調用即可)
   public void setLeftIsVisable(boolean flag){
       if(flag){

         leftButton.setVisibility(View.VISIBLE);

       }else{

        leftButton.setVisibility(View.GONE);
       }
   }

總結:

常用的自定義控件基本如文章所示,自定義View後續補上

本章源碼:

自定義控件源碼

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