android自定义控件概述

摘要:
本文主要描述自定义控件的分类并提供示例代码。Android控件有三种方式:1)派生控件:基于SDK中的现有控件,更改其某些功能以形成符合要求的自定义控件。

引子:

  android SDK中会提供一些基础的控件以供开发。但是大多数情况下,这些基础的控件无法满足业务需求。本文主要说明自定义控件的分类,以及提供示例代码。

  本文只做入门级选手阅读,或者 加深印象 或 温故而知新,大佬大神敬请绕道。

android控件的3种方式:

1)派生控件 :

从SDK已有的控件为基础,改变其部分特征,形成符合需求的自定义控件。


具体做法举例:

public class MyTextView extends EditText(){...}
或者

public class MyListView extends ListView {...}


能够这样做,是因为SDK已有的控件其实已经提供了某些接口让开发者可以进行改造。


示例代码:这是一个继承了EditText,重写了某些函数,调用了EditText自己的API,形成了这种特殊效果,请看示例图。

 1 package com.example.administrator.hankstest0415.custom;
 2 
 3 import android.content.Context;
 4 import android.graphics.Rect;
 5 import android.graphics.drawable.Drawable;
 6 import android.text.Editable;
 7 import android.text.TextWatcher;
 8 import android.util.AttributeSet;
 9 import android.util.Log;
10 import android.view.MotionEvent;
11 import android.widget.EditText;
12 
13 import com.example.administrator.hankstest0415.R;
14 
15 /**17  * 这是一个带删除按钮的EditText,它能够在输入框中有内容时,显示最右边的删除按钮,点击该按钮可以直接清空内容
18  */
19 public class DelEditText extends EditText {
20 
21     private Drawable imgClear;
22     private Context mContext;
23 
24     public DelEditText(Context context, AttributeSet attrs) {
25         super(context, attrs);
26         this.mContext = context;
27         setDrawable();
28         init();
29     }
30 
31     private void init() {
32         imgClear = mContext.getResources().getDrawable(R.drawable.delete);
33         //添加watcher监听器,监听 文本被改变之后的事件
34         addTextChangedListener(new TextWatcher() {
35             //内容变化前
36             @Override
37             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
38 
39             }
40 
41             //内容正在改变
42             @Override
43             public void onTextChanged(CharSequence s, int start, int before, int count) {
44             }
45 
46             //在内容改变完之后
47             @Override
48             public void afterTextChanged(Editable editable) {
49                 Log.d("mytagX", "" + editable.toString());
50                 setDrawable();
51             }
52         });
53     }
54 
55     //绘制删除图片
56     //这里的setCompoundDrawablesWithIntrinsicBounds方法解释一下:
57     //按照原注释的意思,这个方法会在组件的上下左右,如果只需要在右侧显示,那就把其他3个参数设置为null,显示一个Drawable
58     private void setDrawable() {
59         if (length() < 1)//
60             setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
61         else
62             setCompoundDrawablesWithIntrinsicBounds(null, null, imgClear, null);
63     }
64 
65 
66     //当触摸范围在右侧时,触发删除方法,隐藏叉叉
67 
68     /**
69      * 继承父组件的触摸事件
70      *
71      * @param event
72      * @return
73      */
74     @Override
75     public boolean onTouchEvent(MotionEvent event) {
76         if (imgClear != null && event.getAction() == MotionEvent.ACTION_UP) {//如果触发的是 按下并释放的动作,也就是平时的点一下
77             int eventX = (int) event.getRawX();//就拿到当前点击的位置X,Y坐标
78             int eventY = (int) event.getRawY();
79             Log.d("mytagX", "" + eventX + " - " + eventY);
80             Rect rect = new Rect();//新建一个矩形
81             getGlobalVisibleRect(rect);//将当前View的绘制范围大小,设置到这个属性中. 比如说,这个View的绘制范围是 从 (0,0)到(100,200), 那么Rect的4个属性值就是0,0,100,200sa
82 
83             Log.d("onTouchEvent", "" + rect.left + " - " + rect.top + " - " + rect.right + " - " + rect.bottom);
84 
85             rect.left = rect.right - 100;//将rect的左 ,设置为它 右的值-100. 这是在控制触发事件的范围大小
86             if (rect.contains(eventX, eventY))//如果点击的位置,在Rect范围之内,那就触发清空事件
87             {
88                 setText("");
89                 Log.d("onTouchEvent", "点击了EditText并且触发了清空事件");
90             } else
91                 Log.d("onTouchEvent", "点击了EditText但是并没有点击到删除按钮的范围之内");
92 
93         }
94         return super.onTouchEvent(event);
95     }
96 
97 }


示例效果:

android自定义控件概述第1张

 (注:右边的这个图片,是调用EditText的api生成的)

2)自绘控件:

当SDK已有控件完全不能满足需求时,就需要我们直接继承所有控件的父类android.view.View来进行完全的自定义。

能够这么做的基础,就是 继承了View之后,可以重写其中的onDraw方法,使用参数提供的Canvas对象以及 自己创建的paint对象,进行绘图,并且可以调用postInvalidate产生动画效果。

 

示例代码:

  1 package com.example.administrator.hankstest0415.custom;
  2 
  3 import android.content.Context;
  4 import android.graphics.Canvas;
  5 import android.graphics.Paint;
  6 import android.graphics.RectF;
  7 import android.util.AttributeSet;
  8 import android.util.Log;
  9 import android.view.View;
 10 import android.widget.EditText;
 11 
 12 import com.example.administrator.hankstest0415.R;
 13 import com.example.administrator.hankstest0415.util.DensityUtils;
 14 
 15 import org.jetbrains.annotations.Nullable;
 16 
 17 public class PColumn extends View {
 18     int MAX = 100;//最大
 19     int corner = 40;
 20     int data = 0;//显示的数
 21     int tempData = 0;
 22     int textPadding = 20;
 23     Paint mPaint;
 24     int mColor;
 25 
 26     Context mContext;
 27 
 28     //首先,构造函数和 编译器自动生成的方式有所不同
 29     public PColumn(Context context) {
 30         super(context);
 31         mContext = context;
 32     }
 33 
 34     public PColumn(Context context, @Nullable AttributeSet attrs) {
 35         super(context, attrs);
 36         mContext = context;
 37         initPaint();
 38     }
 39 
 40     public PColumn(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 41         super(context, attrs, defStyleAttr);
 42         mContext = context;
 43         initPaint();
 44     }
 45 
 46     private void initPaint() {
 47         mPaint = new Paint();
 48         mPaint.setAntiAlias(true);
 49         mColor = mContext.getResources().getColor(R.color.colorPrimary);
 50         mPaint.setColor(mColor);
 51         setData(80, 100);
 52     }
 53 
 54 
 55     private int defaultHeight = 400;
 56     private int defaultWidth = 180;
 57 
 58     /**
 59      * 重写onMeasure,设定控件最小宽高值。
 60      *
 61      * 因为当布局xml中对这个控件设置wrap_content,而 onMeasure方法并没有指定最小宽高值的话,该控件就会默认match_parent.
 62      *
 63      * @param widthMeasureSpec
 64      * @param heightMeasureSpec
 65      */
 66     @Override
 67     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 68         int width = measureDimension(defaultWidth, widthMeasureSpec);
 69         int height = measureDimension(defaultHeight, heightMeasureSpec);
 70         setMeasuredDimension(width, height);//重写onMeasure一定要调用setMeasuredDimension()。
 71     }
 72 
 73 
 74     public int measureDimension(int defaultSize, int measureSpec) {
 75         int result;
 76 
 77         int specMode = MeasureSpec.getMode(measureSpec);
 78         int specSize = MeasureSpec.getSize(measureSpec);
 79 
 80         if (specMode == MeasureSpec.EXACTLY) {//如果直接指定了宽度,比如100dp
 81             result = specSize;
 82         } else {
 83             result = defaultSize;   //UNSPECIFIED 设定一个默认值
 84             if (specMode == MeasureSpec.AT_MOST) {//如果设定宽度match_parent
 85                 result = Math.min(result, specSize);
 86             }
 87         }
 88         //如果既没有指定宽度,也没有设定match_parent,那么,就用之前设定好的默认值
 89         return result;
 90     }
 91 
 92     @Override
 93     protected void onDraw(Canvas canvas) {
 94         super.onDraw(canvas);
 95         if (data == 0) {
 96             mPaint.setTextSize(getWidth() / 2);
 97             RectF oval3 = new RectF(0, getHeight() - DensityUtils.pxTodip(mContext, 20), getWidth(), getHeight());// 设置个新的长方形
 98             //圆角长方形,醉了,drawRoundRect
 99             canvas.drawRoundRect(oval3, DensityUtils.pxTodip(mContext, corner), DensityUtils.pxTodip(mContext, corner), mPaint);
100 
101             canvas.drawText("0",
102                     getWidth() * 0.5f - mPaint.measureText("0") * 0.5f,
103                     getHeight() - DensityUtils.pxTodip(mContext, 20) - 2 * DensityUtils.pxTodip(mContext, textPadding),
104                     mPaint);
105             return;
106         }
107 
108         //防止数值很大的的时候,动画时间过长
109         int step = data / 100 + 1;
110 
111         if (tempData < data - step) {
112             tempData = tempData + step;
113         } else {
114             tempData = data;
115         }
116         //画圆角矩形
117         String S = tempData + "";
118         //一个字和两,三个字的字号相同
119         if (S.length() < 4) {
120             mPaint.setTextSize(getWidth() / 2);
121         } else {
122             mPaint.setTextSize(getWidth() / (S.length() - 1));
123         }
124 
125         float textH = mPaint.ascent() + mPaint.descent();
126         float MaxH = getHeight() - textH - 2 * DensityUtils.pxTodip(mContext, textPadding);
127         //圆角矩形的实际高度
128         float realH = MaxH / MAX * tempData;
129         RectF oval3 = new RectF(0, getHeight() - realH, getWidth(), getHeight());// 设置个新的长方形
130         canvas.drawRoundRect(oval3, DensityUtils.pxTodip(mContext, corner), DensityUtils.pxTodip(mContext, corner), mPaint);
131         //写数字
132         canvas.drawText(S,
133                 getWidth() * 0.5f - mPaint.measureText(S) * 0.5f,
134                 getHeight() - realH - 2 * DensityUtils.pxTodip(mContext, textPadding),
135                 mPaint);
136         if (tempData != data) {
137             postInvalidate();
138         }
139     }
140 
141     /**
142      * 如果只是自定义View,则onLayout方法不需要实现
143      * @param changed
144      * @param left
145      * @param top
146      * @param right
147      * @param bottom
148      */
149     @Override
150     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
151         super.onLayout(changed, left, top, right, bottom);
152     }
153 
154     @Override
155     public void draw(Canvas canvas) {
156         super.draw(canvas);
157     }
158 
159     public void setData(int data, int MAX) {
160         this.data = data;
161         tempData = 0;
162         this.MAX = MAX;
163         postInvalidate();//进行画面刷新
164     }
165 
166 
167 }

示例效果:

 

android自定义控件概述第2张

 

 另外,除了继承View之外,还可以继承SurfaceView. 两者的区别如下:

SurfaceView和View画图的区别:
1、SurfaceView更新图像不依赖主线程,直接用工作线程就行。View则是必须依赖主线程, 还有可能卡住主线程;
2、SurfaceView可以控制帧数,刷新频率。View的帧率则是系统默认的,无法控制。
3、SurfaceView消耗大,View消耗较小。

示例代码:

  2 
  3 import android.content.Context;
  4 import android.graphics.Canvas;
  5 import android.graphics.Color;
  6 import android.graphics.Paint;
  7 import android.util.AttributeSet;
  8 import android.util.Log;
  9 import android.view.SurfaceHolder;
 10 import android.view.SurfaceView;
 11 
 12 import java.util.Date;
 13 
 14 public class CircleClock extends SurfaceView implements SurfaceHolder.Callback, Runnable {
 15 
 16     private Paint mPaint, mPaint_face, mPaint_second, mPaint_minute, mPaint_hour;
 17     private static final String cloclColor = "#000000";
 18     // 子线程标志位
 19     private boolean mIsDrawing;//控制绘制过程的停和走
 20     private Canvas mCanvas;// 保存画布对象为全局变量
 21 
 22     private void initPaint() {
 23         mPaint = new Paint();
 24         mPaint.setColor(Color.parseColor(cloclColor));
 25         mPaint.setStrokeWidth(2);
 26         mPaint.setStyle(Paint.Style.STROKE);
 27         mPaint.setAntiAlias(true);
 28 
 29         mPaint_face = new Paint();
 30         mPaint_face.setColor(Color.parseColor(cloclColor));
 31         mPaint_face.setStrokeWidth(4);
 32         mPaint_face.setStyle(Paint.Style.STROKE);
 33         mPaint_face.setAntiAlias(true);
 34 
 35         mPaint_second = new Paint();
 36         mPaint_second.setColor(Color.parseColor(cloclColor));
 37         mPaint_second.setStrokeWidth(4);
 38         mPaint_second.setStyle(Paint.Style.STROKE);
 39         mPaint_second.setAntiAlias(true);
 40 
 41         mPaint_minute = new Paint();
 42         mPaint_minute.setColor(Color.parseColor(cloclColor));
 43         mPaint_minute.setStrokeWidth(5);
 44         mPaint_minute.setStyle(Paint.Style.STROKE);
 45         mPaint_minute.setAntiAlias(true);
 46 
 47         mPaint_hour = new Paint();
 48         mPaint_hour.setColor(Color.parseColor(cloclColor));
 49         mPaint_hour.setStrokeWidth(6);
 50         mPaint_hour.setStyle(Paint.Style.STROKE);
 51         mPaint_hour.setAntiAlias(true);
 52     }
 53 
 54     public CircleClock(Context context) {
 55         super(context);
 56         initPaint();
 57         initView();
 58     }
 59 
 60     public CircleClock(Context context, AttributeSet attrs) {
 61         super(context, attrs);
 62         initPaint();
 63         initView();
 64     }
 65 
 66     public CircleClock(Context context, AttributeSet attrs, int defStyleAttr) {
 67         super(context, attrs, defStyleAttr);
 68         initPaint();
 69         initView();
 70     }
 71 
 72 
 73     @Override
 74     public void surfaceCreated(SurfaceHolder holder) {
 75         // 一旦被创建成功,就启动动画
 76         reset();
 77     }
 78 
 79     @Override
 80     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
 81 
 82     }
 83 
 84     @Override
 85     public void surfaceDestroyed(SurfaceHolder holder) {
 86         mIsDrawing = false;
 87     }
 88 
 89     @Override
 90     public void run() {
 91         while (mIsDrawing) {
 92             drawClock();//无限循环绘制指针
 93             try {
 94                 Thread.sleep(500);//每隔1000MS绘制一次
 95             } catch (InterruptedException e) {
 96                 e.printStackTrace();
 97             }
 98         }
 99     }
100 
101     private SurfaceHolder holder;
102 
103     private void initView() {
104         holder = getHolder();//获得holder对象
105         holder.addCallback(this);//添加callback
106         setFocusable(true);
107         setFocusableInTouchMode(true);
108         setKeepScreenOn(true);
109     }
110 
111 
112     private int radiusTarget = 80;
113     private int currentRadius = 0;
114 
115     //详细的绘制过程
116 
117     /**
118      * 这个myDraw方法会无限循环调用
119      */
120     private void drawClock() {
121         try {
122             mCanvas = holder.lockCanvas();//
123             mCanvas.drawColor(Color.parseColor("#FFFFFF"));//绘制背景
124             drawClockFace();
125             drawPointer();
126 
127         } catch (Exception e) {
128 
129         } finally {
130             if (mCanvas != null) {
131                 holder.unlockCanvasAndPost(mCanvas);//释放,并且刷新surface
132             }
133         }
134     }
135 
136     private void drawPointer() {
137 
138         //这里逻辑会发生变化,因为我打算将当前系统时间的时分秒提取出来,然后计算出各自的角度,再将3个指针绘制出来
139         Date date = new Date();
140         int hour = date.getHours();
141         int minute = date.getMinutes();
142         int second = date.getSeconds();
143 
144         //先把秒钟指针画出来
145         // 如何把秒钟转化成角度
146         mCanvas.rotate(second * 6);// 表盘一共360度。 一共60秒,所以每走一秒,度数就走6度
147         mCanvas.drawLine(0, 0, radiusTarget * 7 / 10, 0, mPaint_second);//刻度的长度,设定为半径的1/10
148 
149         //再把分钟指针画出来
150 
151         //其实分钟数是一个小数,而不是int
152         //算出真正的分钟数
153         float realMinute = minute + second / 60.0f;
154         Log.d("drawPointer", "" + realMinute);
155         mCanvas.rotate(-second * 6);// 还得先把原来的角度转回去
156         mCanvas.rotate((realMinute * 6));//再旋转分钟的角度,表盘一共360度。 一共60分,所以每走一分,度数就走6度
157         mCanvas.drawLine(0, 0, radiusTarget * 6 / 10, 0, mPaint_minute);//
158 
159         hour = hour % 12;
160 
161         float realHour = hour + minute / 60.0f;
162         mCanvas.rotate(-realMinute * 6);// 还得先把原来的角度转回去
163         mCanvas.rotate((realHour * 30));//再旋转时钟的角度,表盘上一共12个小时,一共360度,所以每一个小时代表的是30度
164         mCanvas.drawLine(0, 0, radiusTarget * 5 / 10, 0, mPaint_hour);//
165     }
166 
167     /**
168      * 画出表盘
169      */
170     private void drawClockFace() {
171         //这些东西都是只需要绘制一次的
172         int w = getWidth();
173         int h = getHeight();
174         int cx = w / 2;
175         int cy = h / 2;
176         mCanvas.drawCircle(cx, cy, currentRadius, mPaint);
177         mCanvas.drawPoint(cx, cy, mPaint);
178 
179         mCanvas.translate(cx, cy);// 转移坐标轴中心,到原点处
180         mCanvas.rotate(-90);//让指针从12点位置开始走,因为原始的是从3点位置。中间差了90度,所以需要逆时针旋转坐标90度
181         for (int i = 1; i <= 60; i++) {//120次循环,绘制表盘
182             mCanvas.rotate(6);//每一次旋转3度,
183             if (i % 5 == 0) {//如果遇到整点,,1,2,3,4,5,6,7,8,9,10,11,12
184                 mCanvas.drawLine(radiusTarget * 8 / 10, 0, radiusTarget, 0, mPaint_face);//就用较粗的画笔画出较长的线条
185             } else
186                 mCanvas.drawLine(radiusTarget * 9 / 10, 0, radiusTarget, 0, mPaint);//否则,就用较细的画笔画出较短的线条
187         }
188     }
189 
190     public void reset() {
191         radiusTarget = getWidth() / 3;
192         currentRadius = radiusTarget;
193         mIsDrawing = true;
194         new Thread(this).start();
195     }
196 }

效果如下:

android自定义控件概述第3张

 


组合控件:

其实这里有两层境界:

1- 继承SDK已有的Layout(比如,public class MyLayout extends FrameLayout)

2-继承所有Layout的父类:ViewGroup;

一般情况下,由于第二种境界需要完全重写onLayout方法,比较复杂。通常情况下,还是会采用 方案1:继承某种已有的Layout类。

 案例如下:比如我们需要做一个 app中常用的 可复用的TitleBar,包含左边按钮,中间文本,以及右边按钮。

首先看:

attr.xml  自定义的属性预设

(熟悉自定义控件的自定义属性的同学应该知道这个,不多做解释了)

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTitleBar">
<attr name="left_text" format="string" />
<attr name="left_drawable" format="reference" />
<attr name="mid_text" format="string" />
<attr name="right_text" format="string" />
<attr name="right_drawable" format="reference" />
<attr name="title_bg_color" format="color" />

</declare-styleable>
</resources>

title_merge.xml 组合控件预设

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

    <LinearLayout
        android:id="@+id/ll_title"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/btn_left"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:background="@android:color/transparent"
            android:gravity="center" />

        <TextView
            android:id="@+id/tv_mid"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center" />

        <Button
            android:id="@+id/btn_right"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:background="@android:color/transparent"
            android:gravity="center" />

    </LinearLayout>
</merge>

MyTitleBar.java java 代码中的自定义控件编写

 1 package com.example.custom_layout.custom;
 2 
 3 import android.content.Context;
 4 import android.content.res.TypedArray;
 5 import android.graphics.Color;
 6 import android.support.annotation.Nullable;
 7 import android.util.AttributeSet;
 8 import android.view.LayoutInflater;
 9 import android.view.View;
10 import android.widget.Button;
11 import android.widget.LinearLayout;
12 import android.widget.TextView;
13 
14 import com.example.custom_layout.R;
15 
16 public class MyTitleBar extends LinearLayout {
17 
18     public MyTitleBar(Context context) {
19         this(context, null);
20     }
21 
22     public MyTitleBar(Context context, @Nullable AttributeSet attrs) {
23         this(context, attrs, 0);
24     }
25 
26     public MyTitleBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
27         super(context, attrs, defStyleAttr);
28         initView(context, attrs);
29     }
30 
31     private void initView(Context context, AttributeSet attrs) {
32         //关键代码:看以下3个参数
33         //第一个,是 将要实例化的布局
34         //第二个,是 设置rootView(注:布局被实例化之后,可以单独作为一块, 也可以跟随在某一个View下面,作为子view)
35         //第三个,设置是否放置在rootView下面,作为子view
36         // 在这里,我们后面两个参数都必须加上。一个是this,本Layout,一个是true,确定添加到LinearLayout下面去作为子view
37         View titleView = LayoutInflater.from(context).inflate(R.layout.title_merge, this, true);
38 
39         Button btn_left = titleView.findViewById(R.id.btn_left);
40         TextView tv_mid = titleView.findViewById(R.id.tv_mid);
41         Button btn_right = titleView.findViewById(R.id.btn_right);
42         LinearLayout ll_title = titleView.findViewById(R.id.ll_title);
43 
44         //1,集合转换,将attrs转换成TypedArray,两个参数,第一个是 attrs,第二个,则是在attrs.xml里面定义的styleable,
45         // 两个参数结合,就是要将布局xml里面获得的参数,与之前定义好的参数对照起来
46         TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTitleBar);
47         //下面来解析attrs(来自xml的可选参数)
48         if (typedArray != null) {
49 
50             String text_mid = typedArray.getString(R.styleable.MyTitleBar_mid_text);
51             tv_mid.setText(text_mid);
52 
53 
54             int right_drawable = typedArray.getResourceId(R.styleable.MyTitleBar_right_drawable, -1);
55             if (right_drawable != -1) {
56                 btn_right.setCompoundDrawablesWithIntrinsicBounds(0, 0, right_drawable, 0);
57             } else {
58                 String text_right = typedArray.getString(R.styleable.MyTitleBar_right_text);
59                 btn_right.setText(text_right);
60             }
61 
62             int leftDrawable = typedArray.getResourceId(R.styleable.MyTitleBar_left_drawable, -1);
63             if (leftDrawable != -1) {
64                 btn_left.setCompoundDrawablesWithIntrinsicBounds(leftDrawable, 0, 0, 0);//优先图片显示
65             } else {
66                 String text_left = typedArray.getString(R.styleable.MyTitleBar_left_text);
67                 btn_left.setText(text_left);
68             }
69 
70             int bg_color = typedArray.getColor(R.styleable.MyTitleBar_title_bg_color, Color.CYAN);//获得颜色,默认蓝绿色
71             ll_title.setBackgroundColor(bg_color);
72 
73             typedArray.recycle();
74         }
75 
76     }
77 }

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:MyTitleBar="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:orientation="vertical"
    tools:context=".MainActivity">

    <com.example.custom_layout.custom.MyTitleBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:text="Hello World!"
        MyTitleBar:left_drawable="@drawable/back"
        MyTitleBar:left_text="left1"
        MyTitleBar:mid_text="mid1"
        MyTitleBar:right_drawable="@drawable/next"
        MyTitleBar:right_text="right1"
        MyTitleBar:title_bg_color="@android:color/holo_blue_bright" />

    <com.example.custom_layout.custom.MyTitleBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:text="Hello World!"
        MyTitleBar:left_text="left2"
        MyTitleBar:mid_text="mid2"
        MyTitleBar:right_text="right2"
        MyTitleBar:title_bg_color="@color/colorPrimary" />

    <com.example.custom_layout.custom.MyTitleBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:text="Hello World!"
        MyTitleBar:left_text="left3"
        MyTitleBar:mid_text="mid3"
        MyTitleBar:right_text="right3"
        MyTitleBar:title_bg_color="@color/colorPrimaryDark" />


</LinearLayout>

最后是  MainActivity.java (可以看见activity内基本是空白,自定义layout使用起来很简单,只需要在activity_main.xml里面写 控件标签即可)

 1 package com.example.custom_layout;
 2 
 3 import android.support.v7.app.AppCompatActivity;
 4 import android.os.Bundle;
 5 
 6 public class MainActivity extends AppCompatActivity {
 7 
 8     @Override
 9     protected void onCreate(Bundle savedInstanceState) {
10         super.onCreate(savedInstanceState);
11         setContentView(R.layout.activity_main);
12     }
13 }

最后的效果:

android自定义控件概述第4张

OK,这就是经常用到的自定义layout。 至于更多更高境界的骚操作,以后研究到了,就更新博客。

  

 

免责声明:文章转载自《android自定义控件概述》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Linux内核死机调试方法总结python调用摄像头拍照并保存下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

xamarin跨平台iOS、Android 与Windows

http://xamarin.csdn.net/ Xamarin是一个行动App开发平台,提供跨平台开发能力,开发人员透过Xamarin开发工具与程序语言,即可开发出iOS、Android 与Windows 等平台的原生(Native) App 应用程序,不须个别使用各平台的开发工具与程序语言,不只是「write-once, run everywhere」...

Android在代码中设置控件的drawableLeft,drawableRight,drawableTop,drawableBottom。

根据业务的需要,要在代码中设置控件的drawableLeft,drawableRight,drawableTop,drawableBottom属性。 我们知道在xml中设置的方法为:android:drawableLeft="@drawable/xxxxx"; 但是在代码中并没有相关的setDrawableLeft等方法。怎么办呢?别担心,api为我们提供...

Android 本地化适配:RTL(right-to-left) 适配清单

本文首发自公众号:承香墨影(ID:cxmyDev),欢迎关注。 一. 序 越来越多的公司 App,都开始淘金海外,寻找更多的机会。然而海外市场千差万别,无论是市场还是用户的使用习惯,都有诸多的不同。 当你接触一款出海 App 的时候,除了需要了解海外 Google Service 的整个生态圈,还要做好不同语言的适配。语言适配最通用的做法就是根据不同系统...

Scrapy进阶知识点总结(三)——Items与Item Loaders

一.Items 抓取的主要目标是从非结构化源(通常是网页)中提取结构化数据。Scrapy蜘蛛可以像Python一样返回提取的数据。虽然方便和熟悉,但Python缺乏结构:很容易在字段名称中输入拼写错误或返回不一致的数据,尤其是在具有许多蜘蛛的较大项目中。 为了定义通用输出数据格式,Scrapy提供了Item类。 Item对象是用于收集数据的简单容器。它们提...

Android图片处理

  相信做Android开发的小伙伴对于Android图片压缩、裁剪一定有很深的印象,今天我将带领大家一起学习一下这个看着高深莫测的知识,以便再以后的学习、工作中可以帮助到大家。   首先我们看一下这个问题出现的原因,当我们需要在我们的应用中添加大量图片是,程序往往会报出OMM问题,这是什么问题呢?是当我们添加的照片过多或过大时,Android就会报出超内...

重新整理 .net core 实践篇—————中间件[十九]

前言 简单介绍一下.net core的中间件。 正文 官方文档已经给出了中间件的概念图: 和其密切相关的是下面这两个东西: IApplicationBuilder 和 RequestDelegate(HttpContext context) IApplicationBuilder : public interface IApplicationBuilde...