标签:防止 count pos set oba 特殊 bottom tag ova
android SDK中会提供一些基础的控件以供开发。但是大多数情况下,这些基础的控件无法满足业务需求。本文主要说明自定义控件的分类,以及提供示例代码。
本文只做入门级选手阅读,或者 加深印象 或 温故而知新,大佬大神敬请绕道。
从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生成的)
当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 }
效果如下:
3)组合控件:
其实这里有两层境界:
1- 继承SDK已有的Layout(比如,public class MyLayout extends FrameLayout)
2-继承所有Layout的父类:ViewGroup;
一般情况下,由于第二种境界需要完全重写onLayout方法,比较复杂。通常情况下,还是会继承某种已有的Layout类。
标签:防止 count pos set oba 特殊 bottom tag ova
原文地址:https://www.cnblogs.com/hankzhouAndroid/p/8973621.html