android中自定义View

时间:2023-02-09 21:42:32

View 的基本概念

这个类是用户接口的基础构件。 View 表示屏幕上的一块矩形区域,负责绘制这个区域和事件
处理。
View 是所有widget类的基类,Widget 类用于创建交互式UI构件(按钮,输入框等)。
View 类的ViewGroup子类是layout的基类,Layout是一个不可见的容器,它保存着
View(或ViewGroup)并定义这些View的layout属性。
可以说View类是用户接口类中最重要的一个类。

IDs: Views 有一个整数相对应,id被用于在View树中找到指定的View。可以在layout文件中
定义 一个唯一的ID, 在Activity的onCreate函数中调用findViewById来查找这个View。
在整个树内, id可以不是唯一的,但再指定的范围内查找时我们可以确信它是唯一的。
View是一个矩形区域, 使用左&上的坐标以及长和宽可以表示一个View。我们可以使用方
法 getLeft() , getTop() , getRight() , getBottom() , getWidth() , getHeight() 等函数来
获取其位置信息

View创建概述
在API中对View的回调流程有以个详细的描述:
1. Creation:创建流程
2. 1.调用构造器
3. public View(Context context) //使用java代码创建View时的构造方法
4. public View(Context context, AttributeSet attrs)//在XML中配置时的构造方法,attrs存储xml中设置的属性
5. 2.onFinishInflate() //当View和他的所有子View从XML中解析完成后调用。
6. Layout:布局流程
7. 1.onMeasure(int, int) //确定View和它所有的子View要求
的尺寸时调用
8. 2.onLayout(boolean, int, int,int, int) //当这个View为其所有的子View指
派一个尺寸和位置时调用
9. 3.onSizeChanged(int, int, int,int) //当这个View的尺寸改变后调用
10. Drawing:绘制过程
11. 1.onDraw(Canvas) //当View给定其内容时调用
12. Event:事件流程
13. 1.onKeyDown(int, KeyEvent) //当一个新的键按下时
14. 2.onKeyUp(int, KeyEvent) //当一个键弹起时
15. 3.onTrackballEvent(MotionEvent) //当滚迹球事件发生时
16. 4.onTouchEvent(MotionEvent) //当一个触摸屏事件发生时
17. Focus:焦点流程
18. 1.onFocusChanged(boolean, int,Rect) //当View得到和失去焦点时调用
19. 2.onWindowFocusChanged(boolean) //当Window包含的View得到或失去焦点时调用。

定制View

为了实现一个定制View, 需要重写一些View的标准方法。
framework会调用这些方法, 并且认为这些方法应该是所有的view都有实现。
这些方法不必全部重写, 事实上,可以只重写onDraw 函数就可以了
自定义View的步骤:
扩展View或者View的子类。
必须实现其中一个构造方法
重写onDraw(canvas)方法进行绘制
如需调整大小,重写onMesure,默认是全屏的。
如需样式在xml中布置,自定义属性。

测量布局MeasureSpec

在View系统中,指定宽和高,以及指定布局的属性,是由MeasureSpec来封装的。下面是各
个模式的标志位表示。

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any
constraint
* on the child. It can be whatever size it wants.
*/

public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an
exact size
* for the child. The child is going to be given those bounds
regardless
* of how big it wants to be.
*/

public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it
wants up
* to the specified size.
. */

public static final int AT_MOST = 2 << MODE_SHIFT;

在这个解析系统中是通过移位来存放更多的数据,现在每个数据标志位都向左移动了30位。这
样表示一个View大小是很方便的,我们来看下面的方法:

 public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}

通过这个方法就可以制作一个含有两个参数的int值,这个参数包含一个mode标志和一个宽或
高的表示。我们通过如下方法来获取到mode:

public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}

我们也可以用下面方法来获取高或宽的数据表示:

 public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

测量这个View的高和宽。通过调用这个方法来设置View的测量后的高和宽,其最终调用的方
法是:

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= MEASURED_DIMENSION_SET;
}

可见其最终是将高和宽保存在 mMeasuredWidth 、 mMeasuredHeight 这两个参数中。

View

构造方法 只有两个
1. View(Context context) 在使用java代码new出来的时候
2. View(Context context,AttributeSet att) 在xml中设置的,系统帮助我们去构建的
onMeasure 测量布局
onLayout 放在布局中的位置
onDraw 绘制视图 只在最初始的时候加载一次。
如果需要改变,必须刷新视图
主线程
invalidate();
子线程
postInvalidate();

定制View

继承View,重写构造方法
重写onDraw来绘制View
重写onMeasure来给定一个默认大小
MeasureSpec 2+30模式 2:mode 30:size(32位即4个字节,其中两位自己代表模式,其余代表大小)

示例:下面diy一个view

android中自定义View

MainActivity 类

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}

MyView 类

public class MyView extends View {

//画笔
Paint paint = new Paint();

Point point = new Point();


//保证xml中能初始化
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

private void init() {
DisplayMetrics dm = getResources().getDisplayMetrics();
//抗锯齿
paint.setAntiAlias(true);
//防抖动
paint.setDither(true);
//设置颜色
paint.setColor(Color.RED); //0xffff0000
paint.setAlpha(128); //透明度 0-255 ,float 0-1
//空心
//paint.setStyle(Paint.Style.STROKE);
//线条加粗
paint.setStrokeWidth(10);

//设置渐变效果
//Shader shader = new LinearGradient(0, 0, dm.widthPixels, dm.heightPixels, Color.RED, Color.BLUE, Shader.TileMode.MIRROR);
//Shader shader = new RadialGradient(dm.widthPixels / 2, dm.heightPixels / 2, 400, Color.RED, Color.BLUE, Shader.TileMode.REPEAT);
Shader shader = new SweepGradient(dm.widthPixels / 2, dm.heightPixels / 2, Color.RED, Color.BLUE);
paint.setShader(shader);

//圆心点
point.set(getResources().getDisplayMetrics().widthPixels / 2, getResources().getDisplayMetrics().heightPixels / 2);

}

public MyView(Context context) {
super(context);
init();
}


// @Override
// protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//// MeasureSpec.AT_MOST;
// //100 100;
// //拼接大小
// // int width = MeasureSpec.makeMeasureSpec(100, MeasureSpec.AT_MOST);
// super.onMeasure(width, width);
// }

Rect rect = new Rect(300, 500, 400, 800);
RectF rectF = new RectF(200f, 0f, 400f, 200f);
RectF rectF2 = new RectF(20f, 400f, 200f, 700f);

//绘制方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制圆
canvas.drawCircle(point.x, point.y, 100, paint);

//点
canvas.drawPoint(15, 15, paint);

canvas.drawPoints(new float[]{30, 30, 40, 50, 60, 60, 70, 80}, paint);
//线条
canvas.drawLine(100, 50, 300, 300, paint);
//矩形
canvas.drawRect(rect, paint);

//弧线
canvas.drawArc(rectF, 45, 270, true, paint);

//圆角矩形
canvas.drawRoundRect(rectF2, 10, 10, paint);
paint.setTextSize(30);
paint.setTextAlign(Paint.Align.CENTER);
//绘制文本
canvas.drawText("我是绘制的文本", 10, 200, paint);

canvas.drawText("我是居中的文字", getResources().getDisplayMetrics().widthPixels / 2, 300, paint);

canvas.drawText("abcdefg", 0, paint.getTextSize(), paint);
}


}

main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.administrator.lesson12_diyview.MainActivity">


<com.example.administrator.lesson12_diyview.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</RelativeLayout>

示例2:diy一个progressbar

android中自定义View

MainActivity 类

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArcProgressBar apb = (ArcProgressBar) findViewById(R.id.apb);
apb.setOnProgressCompleteListener(new ArcProgressBar.OnProgressCompleteListener() {
@Override
public void onFinish() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getBaseContext(), "执行完毕", Toast.LENGTH_LONG).show();
}
});

}
});
apb.start();
}
}

ArcProgressBar 类

public class ArcProgressBar extends View {

Paint paint;
Paint textPaint;

private int max = 100;

private int progress = 0;


int width;
private RectF oval;

public ArcProgressBar(Context context) {
super(context);
init();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量布局之后 才能获取宽度
width = getWidth();
oval = new RectF(width / 2 - 100, width / 2 - 100, width / 2 + 100, width / 2 + 100);
}

public ArcProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
//获取自定义属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ArcProgressBar);
//取出我们的属性
max = a.getInt(R.styleable.ArcProgressBar_max, 100);
progress = a.getInt(R.styleable.ArcProgressBar_progress, 0);
//一定要释放
a.recycle();
init();
}

private void init() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setDither(true);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setColor(Color.BLUE);
textPaint.setTextSize(30);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//灰色圆在背后
paint.setColor(Color.GRAY);
canvas.drawCircle(width / 2, width / 2, 100, paint);
//绘制一个弧线 进度
paint.setColor(Color.RED);
//结束角度 进度/max 50/100; 0.5 *360
canvas.drawArc(oval, -90, (int) (progress * 1f / max * 360), false, paint);
//绘制百分比文本
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText((int) (progress * 1f / max * 100f) + "%", width / 2, width / 2, textPaint);
}

//模拟启动
public void start() {
new Thread() {
@Override
public void run() {
while (progress < max) {
progress++;
try {
Thread.sleep(80);
postInvalidate();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (onProgressCompleteListener != null) {
onProgressCompleteListener.onFinish();
}
}
}.start();
//主线程刷新
// invalidate();
}


//1.创建监听
public interface OnProgressCompleteListener {
void onFinish();
}

//2.创建接口对象
OnProgressCompleteListener onProgressCompleteListener;

//3. set方法
public void setOnProgressCompleteListener(OnProgressCompleteListener onProgressCompleteListener) {
this.onProgressCompleteListener = onProgressCompleteListener;
}
}

mian_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.administrator.lesson12_diyprogressbar.MainActivity">


<com.example.administrator.lesson12_diyprogressbar.ArcProgressBar
android:id="@+id/apb"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:max="500"
app:progress="23" />

</RelativeLayout>