这一定是最简单的自定义布局

说明

  • 上半部分下载按钮为主页面

  • 下半部分进度条和取消按钮为引用的自定义布局



简介


啥?自定义 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



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