• 《第一行代码》阅读笔记(八)——自定义控件


    在开发中我们经常会需要一些重复使用的控件或者控件组合,就比如安卓系统的三大金刚键,或者一些APP的顶部栏。如果我们为每个Activity都编写,不仅费时费力,而且维护性比较低。学过Java的朋友都应该知道,遇到这种情况,我们都是封装和抽取,然后再调用,所以安卓也不例外,下面就让我们用一个顶部栏作为例子展开学习吧。

    引入布局

    首先先设计共用布局。因为书上面是用了背景图片的,而笔者没有。所以做了一些调整。结果如下

    源代码为

    <?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="horizontal">
    
        <Button
            android:id="@+id/title_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
    
            android:text="Back"
            android:textColor="#fff" />
    
        <TextView
            android:id="@+id/title_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:layout_weight="1"
            android:layout_marginTop="3dp"
            android:background="#504E4E"
            android:gravity="center"
            android:text="Title Text"
            android:textColor="#fff"
            android:textSize="27sp" />
    
        <Button
            android:id="@+id/title_edit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Edit"
            android:textColor="#fff" />
    
    
    </LinearLayout>
    

    这里笔者有一个疑问,就是书中对所有控件都加了android:layout_gravity="center",但是这显然不对啊,添加后控件会变到屏幕中间,所有笔者将其改成了top。而且为了让三个控件可以对齐,笔者取消了按钮控件的margin,同时给textview加了一个margin。并且把textview的底色调成灰色了。

    这里
    android:background是添加背景的,可以是图片,也可以是颜色。
    android:layout_margin是可以指定控件在上下左右方向上偏移的距离,同时可以android:layout_marginTop、android:layout_marginLeft等

    在以后还会遇到一个和边界有关的属性,就是padding。

    margin:
         需要在border外侧添加空白时;
         空白处不需要背景(色)时;
        上下相连的两个盒子之间的空白,需要相互抵消时。
    padding:
        需要在border内测添加空白时;
        空白处需要背景(色)时;
        上下相连的两个盒子之间的空白,希望等于两者之和时。
    ————————————————
    版权声明:本文为CSDN博主「你与温柔」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_41155191/article/details/82798631

    1是margin,2是padding

    因为我们是在外部编写的布局文件,所以在使用时只需要调用即可,这里很好理解,就像封装的函数的调用。这里有两步,第一步引入,只需要一行代码

    <include layout="@layout/title" />
    

    第二步,因为我们想添加顶部栏,所以需要隐藏之前系统自动生成的

     @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ActionBar actionBar = getSupportActionBar();
            if (actionBar != null) {
                actionBar.hide();
            }
        }
    

    可以通过getSupportActionBar()来获得的ActionBar的实例,然后通过hide()来进行隐藏。

    如何全局隐藏ActionBar

    其实ActionBar也是一个非常实用控件,如果想了解的话可以看看下面的这篇文章
    https://www.cnblogs.com/mjsn/p/6150824.html

    自定义布局

    引入布局可以解决很多问题,但是我们不能再为每个引入的布局绑定事件,这样还是不满足我们的需求。所有我们需要再封装,把事件也封装进入。

    先创建一个类TitleLayout继承LinearLayout,并重写构造函数

    public class TitleLayout extends LinearLayout {
    
        public TitleLayout(Context context, AttributeSet attrs){
            super(context,attrs);
            LayoutInflater.from(context).inflate(R.layout.title, this);
            
        }
    }
    

    ——第一行代码
    首先我们重写了LinearLayout中带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这个构造函数。然后在构造函数中需要对标题栏布局进行动态加载,这就要借助LayoutInflater来实现了。通过LayoutInflater的from ()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件,inflate( )方法接收两个参数,第一个参数是要加载的布局文件的id,这里我们传入R.layout.title, 第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传人this。

    在主活动的布局文件中插入,和普通控件一样,只不过需要使用全类名。

    <com.firstcode.uicustomviews.TitleLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />
    

    修改TitleLayout类,添加响应事件。

    public class TitleLayout extends LinearLayout {
    
        public TitleLayout(Context context, AttributeSet attrs){
            super(context,attrs);
            LayoutInflater.from(context).inflate(R.layout.title, this);
            Button titleBack = (Button) findViewById(R.id.title_back);
            Button titleEdit = (Button) findViewById(R.id.title_edit);
            titleBack.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    ((Activity)getContext()).finish();
                }
            });
            titleEdit.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(getContext(),"You clicked Edit button",Toast.LENGTH_SHORT).show();
                }
            });
        }
    
    
    }
    

    这里我看书上使用了一个((Activity)getContext()).finish();不知道什么意思,书上也没解释,我推测是结束当前的环境,不是直接退出程序。但是这个demo只有一个主界面,所有我改成finish()并没有影响。

    自定义控件

    第一步:声明一个类,并继承View

    public class RoundProgressBar extends View {
    

    继承View是比较底层的控件了,其实也可以继承已有的控件,例如之前的LinearLayout或者Button。

    第二步:构造函数

    public RoundProgressBar(Context context) {
    		this(context, null);
    	}
    
    	public RoundProgressBar(Context context, AttributeSet attrs) {
    		this(context, attrs, 0);
    	}
    
    	public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
         }
    

    其实这种方法并不规范, 但是这样编写有个方便的地方,就是不管用哪种方法构造,都会到最后一个函数。

    第三步:添加属性
    在编写构造函数之前,还需要给这个控件添加一些私有属性。在Value中添加一个attr.xml文件

    <declare-styleable name="circleProgressBar">
            <attr name="circleColor" format="color" />
            <attr name="circleProgressColor" format="color" />
            <attr name="circleWidth" format="dimension" />
            <attr name="textColor" format="color" />
            <attr name="textSize" format="dimension" />
            <attr name="max" format="integer" />
            <attr name="textIsDisplayable" format="boolean" />
            <attr name="newCircleMargin" format="dimension" />
            <attr name="style">
                <enum name="STROKE" value="0" />
                <enum name="FILL" value="1" />
            </attr>
        </declare-styleable>
    

    声明一个这样的内容,declare-styleable name标签就是这个控件的名字,然后attr name标签就是属性的名字。

    第四步:编写构造函数

    1. 首先声明一个paint类paint = new Paint();
    2. 实例化TypedArrayTypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.circleProgressBar);
    3. 给每个属性赋值roundColor = mTypedArray.getColor( R.styleable.circleProgressBar_circleColor, Color.RED);
    4. 回收 mTypedArray.recycle();

    第五步:重写onDraw

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

    其中canvas就是一个画布工具,可以对图形做出很多操作。在onDraw中需要对每个属性进行赋值,同时还可以进行一些逻辑的操作。

    安卓中Paint类和Canvas类的方法汇总

    第六步:使用自定义控件

    <com.core.common.widget.RoundProgressBar
                        android:id="@+id/testlib_in_totalProgress"
                        android:layout_width="35dp"
                        android:layout_height="35dp"
                        android:layout_centerHorizontal="true"
                        android:layout_marginRight="25dp"
                        android:background="@drawable/ic_totalprogress"
                        android:clickable="true"
                        app:circleColor="#d8d8d8"
                        app:circleProgressColor="@color/blue"
                        app:circleWidth="2dp" />
    

    其中android就是系统自带的属性,app就是自定义的属性。注意使用全类名!

  • 相关阅读:
    栈(代码分解)
    线性表(代码分解)
    绪论简概
    1006 Sign In and Sign Out (25 分)
    1005 Spell It Right (20 分)
    分支界限法解决0/1背包问题
    Sequence( 分块+矩阵快速幂 )
    Shape Number (最小表示法)
    Age of Moyu (2018 Multi-University Training Contest 7)
    [Cqoi2014]危桥 (两遍网络流)
  • 原文地址:https://www.cnblogs.com/zllk/p/13363709.html
Copyright © 2020-2023  润新知