• android自定义控件概述


    引子:

      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 }


    示例效果:

     (注:右边的这个图片,是调用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 }

    示例效果:

     

     

     另外,除了继承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 }

    效果如下:

     


    组合控件:

    其实这里有两层境界:

    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 }

    最后的效果:

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

      

     

  • 相关阅读:
    linux | 管道符、输出重定向
    php 升级php5.5 、php7
    mysql 启动失败
    centos7.2安装phpmyadmin
    php file_get_contents失败[function.file-get-contents]: failed to open stream: HTTP request failed!解决
    go println与printf区别
    前端 head 中mate 详解
    centos 7 安装mysql
    iOS数据持久化—数据库SQLite模糊查询
    C 语言字符串和格式化输入与输出
  • 原文地址:https://www.cnblogs.com/hankzhouAndroid/p/8973621.html
Copyright © 2020-2023  润新知