這一定是最簡單的自定義佈局

說明

  • 上半部分下載按鈕爲主頁面

  • 下半部分進度條和取消按鈕爲引用的自定義佈局



簡介


啥?自定義 View 和自定義佈局不一樣?沒錯,自定義 View 是畫布局,重在畫,從 0 到 1;自定義佈局是組合控件,重在組合,將分散的多個控件組合成一個整體。所以意義上是不一樣的,當然廣義上都可以叫自定義 View,畢竟殊途同歸。這裏對 View 不做介紹,需要了解的可以參考這篇文章進階之路-自定義View


思路


需求:


  • 外部類可以在代碼裏和xml裏分別設置進度條的最大值(max),進度值(progress


整體思路:


  • 代碼設置需求可以提供外部類三個方法:setMax(),setProgress() 和 setBtnCancelListener()。setBtnCancelListener 用來取消下載監聽(免費贈送的) 

  • xml 設置需求可以通過 attrs.xml 自定義屬性實現


實現分三大步:


一:自定義佈局動態設置屬性(代碼需求)

二:自定義佈局靜態設置屬性(xml需求,若沒有xml需求這一步可以省略)

三:外部類調用

實現


第一步,自定義佈局動態設置屬性。我們使用某個控件一般都需要在代碼裏動態設置其屬性或者方法,所以現在先說這一步的實現。分爲4小步


1. 新建佈局。本例中是一個ProgressBar+Button,和平時寫的沒有任何區別,放在layout文件夾下即可

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
   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">

   <ProgressBar
       android:id="@+id/pbar_progress"
       style="?android:attr/progressBarStyleHorizontal"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>

   <Button
       android:id="@+id/btn_cancel"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="取消"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toBottomOf="@id/pbar_progress"/>

</android.support.constraint.ConstraintLayout>



效果圖如下:



2. 新建自己的View類。新建一個類繼承LinearLayout並重寫下面兩個構造方法,爲啥要繼承線性佈局呢,你喜歡的話也可以繼承相對佈局,建議先學會再研究

/**
* 自定義組件
* Created by wangjiong on 2017/12/18.
*/

public class CompoundView extends LinearLayout {
   public CompoundView(Context context) {
       super(context);
   }
   public CompoundView(Context context, @Nullable AttributeSet attrs) {
       super(context, attrs);
   }
}


3. 關聯佈局。可以看到我們通過LayoutInflater類將第 1 步中的佈局轉化爲view,然後通過這個view獲取到裏面的控件,並在兩個構造方法中都實現了此步驟

/**
* 自定義組件
* Created by wangjiong on 2017/12/18.
*/

public class CompoundView extends LinearLayout {
   private ProgressBar pbar;
   private Button btnCancel;
   public CompoundView(Context context) {
       super(context);
       // 初始化佈局
       initView(context);
   }
   public CompoundView(Context context, @Nullable AttributeSet attrs) {
       super(context, attrs);
       // 初始化佈局
       initView(context);
   }
   /**
    * 初始化佈局
    */

   private void initView(Context context) {
       // 將佈局導入到LinearLayout中
       View view = LayoutInflater.from(context).inflate(R.layout.view_compound, this);
       pbar = (ProgressBar) view.findViewById(R.id.pbar_progress);
       btnCancel = (Button) view.findViewById(R.id.btn_cancel);
   }
}


4. 設置供外部類調用的方法。可以看到除了 onFinishInflate() 方法之外,新增的 setMax(int max),setProgress(int progress) 和 setBtnCancelListener(OnClickListener lickListener) 這三個方法應該沒啥難度。那麼重寫的onFinishInflate()是幹嘛的呢?就像註釋寫的那樣,官方話:子控件均被映射成XML文件才觸發的。說人話:在xml佈局中靜態設置屬性值之後,在外部類動態設置 setMax(int max) 等方法之前調用。所以如果你沒有自定義屬性,那麼這個方法完全可以不重寫。其實到這裏已經實現了自定義佈局,外部類的佈局中可以正常使用這個CompoundView ,外部類的代碼中可以正常調用CompoundView 定義的方法

/**
* 自定義組件
* Created by wangjiong on 2017/12/18.
*/

public class CompoundView extends LinearLayout {
   private ProgressBar pbar;
   private Button btnCancel;
   private int max = 100;// 最大進度(這裏默認爲100,自己可以隨便設置,看業務)
   private int progress = 0;// 當前進度(這裏默認爲0,自己可以隨便設置,看業務)
   /**
    * 設置最大值
    *
    * @param max 最大值
    */

   public void setMax(int max) {
       this.max = max;
       pbar.setMax(max);
   }
   /**
    * 設置當前進度
    *
    * @param progress 進度
    */

   public void setProgress(int progress) {
       if (progress > max) {
           this.progress = max;
       } else {
           this.progress = progress;
       }
       pbar.setProgress(this.progress);
   }
   public void setBtnCancelListener(OnClickListener lickListener) {
       btnCancel.setOnClickListener(lickListener);// 給取消按鈕設置監聽(這裏也可以用接口回調供外部類調用)
   }
   /**
    * 子控件均被映射成XML文件才觸發的(在xml靜態設置屬性之後,動態設置屬性之前)
    */

   @Override
   protected void onFinishInflate() {
       super.onFinishInflate();
       pbar.setMax(max);
       pbar.setProgress(progress);
   }
   public CompoundView(Context context) {
       super(context);
       // 初始化佈局
       initView(context);
   }
   public CompoundView(Context context, @Nullable AttributeSet attrs) {
       super(context, attrs);
       // 初始化佈局
       initView(context);
   }
   /**
    * 初始化佈局
    */

   private void initView(Context context) {
       // 將佈局導入到LinearLayout中
       View view = LayoutInflater.from(context).inflate(R.layout.view_compound, this);
       pbar = (ProgressBar) view.findViewById(R.id.pbar_progress);
       btnCancel = (Button) view.findViewById(R.id.btn_cancel);
   }
}


第二步,自定義佈局靜態設置屬性。分2小步


1. 新建 attrs.xml 佈局自定義屬性。在 res/values 下新建 attrs.xml 文件,declare-styleable 對標籤的 name 要和自定義類名相同,attr 對標籤的 name 可以隨便寫,簡單易懂即可,format 值爲對應的數據類型,這裏我們根據業務設置了 integer 類型

<resources>
   <!--自定義控件-->
   <declare-styleable name="CompoundView">
       <attr name="max" format="integer"/>
       <attr name="progress" format="integer"/>
   </declare-styleable>
</resources>


2. 自定義類裏初始化自定義屬性。在第二個構造方法中多出了 4 行代碼,這 4 行代碼的作用就是將你引用此自定義佈局時在xml設置的值取出來,然後分別賦給對應的成員變量

/**
* 自定義組件
* Created by wangjiong on 2017/12/18.
*/

public class CompoundView extends LinearLayout {
   private ProgressBar pbar;
   private Button btnCancel;
   private int max = 100;// 最大進度(這裏默認爲100,自己可以隨便設置,看業務)
   private int progress = 0;// 當前進度(這裏默認爲0,自己可以隨便設置,看業務)
   /**
    * 設置最大值
    *
    * @param max 最大值
    */

   public void setMax(int max) {
       this.max = max;
       pbar.setMax(max);
   }
   /**
    * 設置當前進度
    *
    * @param progress 進度
    */

   public void setProgress(int progress) {
       if (progress > max) {
           this.progress = max;
       } else {
           this.progress = progress;
       }
       pbar.setProgress(this.progress);
   }
   public void setBtnCancelListener(OnClickListener lickListener) {
       btnCancel.setOnClickListener(lickListener);// 給取消按鈕設置監聽(這裏也可以用接口回調供外部類調用)
   }
   /**
    * 子控件均被映射成XML文件才觸發的(在xml靜態設置屬性之後,動態設置屬性之前)
    */

   @Override
   protected void onFinishInflate() {
       super.onFinishInflate();
       pbar.setMax(max);
       pbar.setProgress(progress);
   }
   public CompoundView(Context context) {
       super(context);
       // 初始化佈局
       initView(context);
   }
   public CompoundView(Context context, @Nullable AttributeSet attrs) {
       super(context, attrs);
       // 初始化佈局
       initView(context);
       // 自定義佈局靜態設置屬性。這一步會將你引用此自定義佈局時在xml設置的值取出來
       TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CompoundView);
       max = typedArray.getInt(R.styleable.CompoundView_max, 100);
       progress = typedArray.getInt(R.styleable.CompoundView_progress, 10);
       typedArray.recycle();
   }
   /**
    * 初始化佈局
    */

   private void initView(Context context) {
       // 將佈局導入到LinearLayout中
       View view = LayoutInflater.from(context).inflate(R.layout.view_compound, this);
       pbar = (ProgressBar) view.findViewById(R.id.pbar_progress);
       btnCancel = (Button) view.findViewById(R.id.btn_cancel);
   }
}


第三步,外部類調用。以上兩大步咱們的自定義佈局就已經完成了,接着用外部類調用的方式測試一下效果。很簡單,一個點擊下載的按鈕,一個自定義類的引用。

其中命名空間

xmlns:wj="http://schemas.android.com/apk/res-auto"

這個命名空間可以自定義名字,這裏我用的 wj,如果你用了app,那麼ConstraintLayout類裏只需要一個就行啦。

xmlns:app="http://schemas.android.com/apk/res-auto"

友情提示,咱們自定義的屬性在外部使用的時候需要命名空間才能使用哦,就像安卓自己的屬性在使用時同樣需要加命名空間,然後屬性前綴爲android:

xmlns:android="http://schemas.android.com/apk/res/android"


  • 外部類的佈局裏驗證。wj:max 和 wj:progress 爲自定義屬性

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
   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"
   xmlns:wj="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.wj.test.activity.CustomWidgetActivity">

   <Button
       android:id="@+id/btn_down"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="下載"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>

   <!--命名空間的前綴是可以自定義的,只要保證上下一致即可。這裏我用了wj-->
   <com.wj.test.view.CompoundView
       android:id="@+id/cv_progress"
       android:layout_width="0dp"
       android:layout_height="wrap_content"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toBottomOf="@id/btn_down"
       wj:max="50"
       wj:progress="5"/>

</android.support.constraint.ConstraintLayout>


  • 外部類的代碼中驗證。如果同時在xml裏和代碼裏設置屬性,那麼代碼的設置會覆蓋掉xml的,常識哦

/**
* 外部類測試自定義控件
* Created by wj on 2017/12/19 17:05
*/

public class CustomWidgetActivity extends AppCompatActivity {
   private CompoundView view;// 自定義控件對象
   private boolean isNotFinish = true;// 是否結束
   private int i;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_custom_widget);
       view = (CompoundView) findViewById(R.id.cv_progress);
       view.setMax(100);// 設置最大值
       view.setProgress(90);// 設置當前進度
       view.setBtnCancelListener(new View.OnClickListener() {// 取消監聽
           @Override
           public void onClick(View v) {
               isNotFinish = false;
               i = 0;
               view.setProgress(i);
           }
       });
       Button down = (Button) findViewById(R.id.btn_down);
       down.setOnClickListener(new View.OnClickListener() {// 下載監聽
           @Override
           public void onClick(final View v) {
               isNotFinish = true;
               // 子線程模擬進度
               new Thread() {
                   @Override
                   public void run() {
                       while (isNotFinish && i <= 100) {
                           try {
                               Thread.sleep(1000);
                               // 主線程更新UI
                               runOnUiThread(new Runnable() {
                                   @Override
                                   public void run() {
                                       view.setProgress(i++);
                                   }
                               });
                           } catch (InterruptedException e) {
                               e.printStackTrace();
                           }
                       }
                   }
               }.start();
           }
       });
   }
}



全劇終


源碼地址:https://github.com/GodJiong/MyApplication



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