重写一个view一般情况下只需要重写onDraw()方法。那么什么时候需要重写onMeasure()、onLayout()、onDraw()
方法呢,这个问题只要把这几个方法的功能弄清楚就应该知道怎么做了。
①、如果需要绘制View的图像,那么需要重写onDraw()方法。(这也是最常用的重写方式。)
②、如果需要改变view的大小,那么需要重写onMeasure()方法。
③、如果需要改变View的(在父控件的)位置,那么需要重写onLayout()方法。
④、根据上面三种不同的需要你可以组合出多种重写方案。
Paint类代表画笔,用来描述图形的颜色和风格,如线宽,颜色,透明度,和填充效果等信息,使用Paint时,需要先创建该类的对象,这可以通过该类提供的构造方法来实现。通常情况下,只需要使用无参数的构造方法来创建一个使用默认设置的Paint对象:
Paint paint = new Paint();
Android提供了强大的二维图形库,比较常用的是绘制几何图形,绘制文本,路径和图片等
比较常见的图形包括点,线,弧,圆形,矩形,在Android中,Canvas类提供了丰富的绘制几何图形的方法,通过这些方法可以绘制出各种几何图形
//距离左上为0的矩形中半径为150/2的弧
canvas.drawArc(0,0,150,150,0,90,true,mPaint)
//圆心为(100,100),半径为100
canvas.drawCircle(100,100,100,mPaint);
int height = getHeight();
int width = getWidth();
canvas.drawLine(0,height/2,width,height/2,mPaint);
canvas.drawText("A1(20,100)", 0, 90, mPaint);
canvas.drawText("A2(100,250)", 40, 270, mPaint);
canvas.drawText("B1(100,100)", 80, 90, mPaint);
canvas.drawText("B2(180,250)", 150, 270, mPaint);
float[] points=new float[]{20,100,100,250,100,250,100,100,100,100,180,250};//至少4个值,即能够绘制一条直线
canvas.drawLines(points,mPaint);
canvas.drawText("A1(100,250)", 40, 270, mPaint);
canvas.drawText("B2(100,100)", 80, 90, mPaint);
canvas.drawText("B3(180,250)", 150, 270, mPaint);
float[] points=new float[]{20,100,100,250,100,250,100,100,100,100,180,250};//至少4个点
canvas.drawLines(points,4,8,mPaint);
//x轴占200,y轴占100的椭圆
canvas.drawOval(0,0,200,100,mPaint);
canvas.drawPoint(100,100,mPaint);
float[] points=new float[]{20,100,100,250,100,100,180,250};//至少2个点
canvas.drawPoints(points,mPaint);
//长为200,宽为200-50的矩形
canvas.drawRect(0,50,200,200,mPaint);
canvas.drawRoundRect(0,50,200,200,20,20,mPaint);
MeasureSpec是View的内部类,它封装了一个View的尺寸,在onMeasure()当中会根据这个MeasureSpec的值来确定View的宽高。
MeasureSpec的值保存在一个int值当中。一个int值有32位,前两位表示模式mode后30位表示大小size。即MeasureSpec = mode + size。
在MeasureSpec当中一共存在三种mode:UNSPECIFIED、EXACTLY 和AT_MOST
public class TestView extends View {
/**
* 在java代码里new的时候会用到
* @param context
*/
public TestView(Context context) {
super(context);
}
/**
* 在xml布局文件中使用时自动调用
* @param context
*/
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
/**
* 不会自动调用,如果有默认style时,在第二个构造函数中调用
* @param context
* @param attrs
* @param defStyleAttr
*/
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 只有在API版本>21时才会用到
* 不会自动调用,如果有默认style时,在第二个构造函数中调用
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
Android系统的控件以android开头的(比如android:layout_width)都是系统自带的属性。为了方便配置自定义控件的属性,我们也可以自定义属性。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LayoutView">
<attr name="background_view" format="color" />
</declare-styleable>
</resources>
<com.example.customview.view.LayoutView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="50dp"
android:text="这是一个TextView的自定义View"
app:background_view="@color/colorAccent"/>
public LayoutView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LayoutView);
int color = typedArray.getColor(R.styleable.LayoutView_background_view, Color.BLACK);
setBackgroundColor(color);
//获取资源后要及时回收
typedArray.recycle();
}
//如:设置为背景
setBackgroundColor(color);
<attr name = "background" format = "reference" />
<ImageView android:background = "@drawable/图片ID" />
<attr name = "textColor" format = "color" />
<TextView android:textColor = "#00FF00" />
<attr name = "focusable" format = "boolean" />
<Button android:focusable = "true" />
<attr name = "layout_width" format = "dimension" />
<Button android:layout_width = "42dip" />
<attr name = "fromAlpha" format = "float" />
<alpha android:fromAlpha = "1.0" />
<attr name = "framesCount" format="integer" />
<animated-rotate android:framesCount = "12" />
<attr name = "text" format = "string" />
<TextView android:text = "我是文本" />
<attr name = "pivotX" format = "fraction" />
<rotate android:pivotX = "200%" />
<declare-styleable name="名称">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
<LinearLayout
android:orientation = "vertical">
</LinearLayout>
注意:枚举类型的属性在使用的过程中只能同时使用其中一个,不能 android:orientation = “horizontal|vertical"
<declare-styleable name="名称">
<attr name="gravity">
<flag name="top" value="0x01" />
<flag name="bottom" value="0x02" />
<flag name="left" value="0x04" />
<flag name="right" value="0x08" />
<flag name="center_vertical" value="0x16" />
...
</attr>
</declare-styleable>
<TextView android:gravity="bottom|left"/>
注意:位运算类型的属性在使用的过程中可以使用多个值
<declare-styleable name = "名称">
<attr name = "background" format = "reference|color" />
</declare-styleable>
<ImageView
android:background = "@drawable/图片ID" />
或者:
<ImageView
android:background = "#00FF00" />
继承控件的意思就是,我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能。
继承EditText实现一个记事本
package com.example.customview.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.EditText;
public class NotePad extends EditText {
private int lineWidth = 1;
private int lineColor = Color.BLACK;
private int spacing_line = 20;
private int padding = 10;
private Paint paint;
public NotePad(Context context) {
super(context);
}
public NotePad(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusableInTouchMode(true);
//设置光标处于左上角
setGravity(Gravity.TOP);
//设置行间距
setLineSpacing(spacing_line, 1);
setPadding(padding, 10, padding, 10);
paint = new Paint();
paint.setColor(lineColor);
paint.setStrokeWidth(lineWidth);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获得当前控件的高度
int height = getHeight();
//获得每一行的高度
int lineHeight = getLineHeight();
//计算出每页的行数
int pageCount = height / lineHeight;
for (int i = 0; i < pageCount; i++) {
canvas.drawLine(padding, (i + 1) * lineHeight, getWidth() - padding, (i + 1) * lineHeight, paint);
}
}
}
继承控件的意思就是,我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能。
实现自定义时钟
package com.example.customview.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import java.util.Calendar;
public class AlarmClockView extends View {
private Paint circlePaint = new Paint();
private Paint circlePaint2 = new Paint();
private Paint dotPaint = new Paint();
private Paint hourPaint = new Paint();
private Paint minutePaint = new Paint();
private Paint secondPaint = new Paint();
private Calendar calendar;
public AlarmClockView(Context context) {
super(context);
initDraw();
}
public AlarmClockView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initDraw();
}
public AlarmClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initDraw();
}
private void initDraw() {
circlePaint.setColor(Color.BLUE);
circlePaint.setStrokeWidth(8);
//设置填充效果为空心
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint2.setColor(Color.BLUE);
circlePaint2.setStrokeWidth(4);
circlePaint2.setStyle(Paint.Style.STROKE);
dotPaint.setColor(Color.BLUE);
dotPaint.setStrokeWidth(2);
dotPaint.setStyle(Paint.Style.STROKE);
hourPaint.setStrokeWidth(4);
minutePaint.setStrokeWidth(3);
secondPaint.setStrokeWidth(2);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
calendar = Calendar.getInstance();
int radius = width / 2 - 10;
//画大小3个圆
canvas.drawCircle(width / 2, height / 2, radius, circlePaint);
canvas.drawCircle(width / 2, height / 2, radius - 15, circlePaint2);
canvas.drawCircle(width / 2, height / 2, 15, dotPaint);
//绘制12个刻钟的位置
for (int i = 1; i < 13; i++) {
//在旋转之前保存画布状态
canvas.save();
canvas.rotate(i * 30, width / 2, height / 2);
//1.2表示起点坐标,3.4表示终点坐标,5.画笔
canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 35, circlePaint2);
//恢复画布状态
canvas.restore();
}
//获得当前小时
int hour = calendar.get(Calendar.HOUR);
//在旋转之前保存画布状态
canvas.save();
//旋转
canvas.rotate(hour * 30, width / 2, height / 2);
//画时针
canvas.drawLine(width / 2, height / 2 + 20, width / 2, height / 2 - 140, hourPaint);
//恢复画布状态
canvas.restore();
//获得当前分钟
int minute = calendar.get(Calendar.MINUTE);
//在旋转之前保存画布状态
canvas.save();
//旋转
canvas.rotate(minute * 6, width / 2, height / 2);
//画分针
canvas.drawLine(width / 2, height / 2 + 30, width / 2, height / 2 - 170, minutePaint);
//恢复画布状态
canvas.restore();
//获得当前秒数
int second = calendar.get(Calendar.SECOND);
//在旋转之前保存画布状态
canvas.save();
//旋转
canvas.rotate(second * 6, width / 2, height / 2);
//画秒针
canvas.drawLine(width / 2, height / 2 + 40, width / 2, height / 2 - 200, secondPaint);
//恢复画布状态
canvas.restore();
//每隔1秒重绘View,重绘会调用onDraw()方法
postInvalidateDelayed(1000);
}
}
本文地址:https://blog.csdn.net/m0_46211029/article/details/107365532
如对本文有疑问, 点击进行留言回复!!
【Appium踩坑】小米手机,启动报错:exited with code 255 writing to settings requires:android.permission.WRITE_SECUR
android 拍照 预览图与 照片分辨率(可视区域)不一致
[PAT顶级]1025 Keep at Most 100 Characters (35分)
Android 天气APP(二十)增加欢迎页及白屏黑屏处理、展示世界国家/地区的城市数据
Android使用SharedPreferences保存List列表数据
解决android sdk 运行出现 could not install *smartsocket* listener: cannot bind to 127.0.0.1:5037:的问题
网友评论