自定义view
一、自定义view实现方式
二、组合控件
将系统原有的控件进行组合,构成一个新的控件。
定义标题栏的布局文件custom_title_view.xml,将返回按钮和标题文本进行组合。这一步用于确定标题栏的样子,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_orange_light">
<Button
android:id="@+id/btn_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:text="Back"
android:textColor="@android:color/white" />
<TextView
android:id="@+id/title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Title"
android:textColor="@android:color/white"
android:textSize="20sp" />
</RelativeLayout>
public class CustomTitleView extends FrameLayout implements View.OnClickListener {
private View.OnClickListener mLeftOnClickListener;
private Button mBackBtn;
private TextView mTittleView;
public CustomTitleView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.custom_title_view, this);
mBackBtn = findViewById(R.id.btn_left);
mBackBtn.setOnClickListener(this);
mTittleView = findViewById(R.id.title_tv);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_left:
if (mLeftOnClickListener != null) {
mLeftOnClickListener.onClick(v);
}
break;
}
}
public void setLeftOnClickListener(View.OnClickListener leftOnClickListener) {
mLeftOnClickListener = leftOnClickListener;
}
public void setTittle(String title){
mTittleView.setText(title);
}
}
使用组合自定义view
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.demos.customviewdemo.CustomTitleView
android:id="@+id/customview_title"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.demos.customviewdemo.CustomTitleView>
</RelativeLayout>
public class CustomViewComposeDemoActivity extends AppCompatActivity {
private CustomTitleView mCustomTitleView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_view_compose_demo);
mCustomTitleView = findViewById(R.id.customview_title);
mCustomTitleView.setTittle("This is Title");
mCustomTitleView.setLeftOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}
效果如下:
三、继承控件
public class HistogramView extends View{
private Paint mPaint;
private Path mPath;
public HistogramView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPath = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制坐标轴
mPaint.reset();
mPath.reset();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPath.moveTo(100,100);
mPath.rLineTo(0,402);
mPath.rLineTo(800,0);
canvas.drawPath(mPath,mPaint);
//绘制文字
mPaint.reset();
mPaint.setTextSize(30);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText("Froyo",160,540,mPaint);
canvas.drawText("CB",280,540,mPaint);
canvas.drawText("ICS",380,540,mPaint);
canvas.drawText("J",480,540,mPaint);
canvas.drawText("KitKat",560,540,mPaint);
canvas.drawText("L",690,540,mPaint);
canvas.drawText("M",790,540,mPaint);
//绘制直方图,柱形图是用较粗的直线来实现的
mPaint.reset();
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(80);
float[] lines3={
200,500,200,495,
300,500,300,480,
400,500,400,480,
500,500,500,300,
600,500,600,200,
700,500,700,150,
800,500,800,350,
};
canvas.drawLines(lines3,mPaint);
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.demos.customviewdemo.HistogramView
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
效果图:
四、自定义View中引入属性
1、在values中编写需要的属性
在res/values/下新建资源文件,这里咱们命名为attrs.xml,在其中编写所需要的属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HistogramView">
<attr name="textColor" format="color"/>
<attr name="histogramColor" format="color"/>
</declare-styleable>
</resources>
public class HistogramView extends View{
private Paint mPaint;
private Path mPath;
private int mTextColor,mHistogramColor;//新加的属性使用
public HistogramView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPath = new Path();
initAttrs(context,attrs);//新加的属性使用
}
//新加的属性使用
private void initAttrs(Context context, AttributeSet attrs){
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HistogramView);
mTextColor = typedArray.getColor(R.styleable.HistogramView_textColor,Color.BLACK);
mHistogramColor = typedArray.getColor(R.styleable.HistogramView_histogramColor,Color.GREEN);
typedArray.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制坐标轴
mPaint.reset();
mPath.reset();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPath.moveTo(100,100);
mPath.rLineTo(0,402);
mPath.rLineTo(800,0);
canvas.drawPath(mPath,mPaint);
//绘制文字
mPaint.reset();
mPaint.setTextSize(30);
mPaint.setColor(mTextColor);//新加的属性使用
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText("Froyo",160,540,mPaint);
canvas.drawText("CB",280,540,mPaint);
canvas.drawText("ICS",380,540,mPaint);
canvas.drawText("J",480,540,mPaint);
canvas.drawText("KitKat",560,540,mPaint);
canvas.drawText("L",690,540,mPaint);
canvas.drawText("M",790,540,mPaint);
//绘制直方图,柱形图是用较粗的直线来实现的
mPaint.reset();
mPaint.setColor(mHistogramColor);//新加的属性使用
mPaint.setStrokeWidth(80);
float[] lines3={
200,500,200,495,
300,500,300,480,
400,500,400,480,
500,500,500,300,
600,500,600,200,
700,500,700,150,
800,500,800,350,
};
canvas.drawLines(lines3,mPaint);
}
}
自绘控件基本方法
基本流程
构造函数
自定义view中使用new实例化一个View会调用第一个构造函数,在xml中定义会调用第二个构造函数,而第三个函数系统是不调用的,要显式调用
public class CircleView extends View {
private int mColor = Color.RED;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//使用new实例化一个View会调用第一个构造函数
public CircleView(Context context) {
this(context, null);
}
//使用反射会调用到该函数。在xml中定义会调用第二个构造函数,xml中使用到反射
public CircleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
//系统是不调用的,要显式调用
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.YELLOW);
a.recycle();
init();
}
}
MeasureSpec和LayoutParams的对应关系
系统内部是通过MeasureSpec来进行View的测量,但是正常情况下我们使用View指定MeasureSpec,尽管如此,但是我们可以给View设置LayoutParams。在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽/高。需要注意的是,MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。另外,对于顶级View(DecorView)和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口尺寸和其自身的MeasureSpec来共同决定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。
MeasureSpec 工作原理
MeasureSpec 代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize。
MeasureSpec是View中的一个静态内部类。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
SpecMode是指测量模式,SpecSize是指在某种测量模式下的大小。
SpecMode有三类:
UNSPECIFIED
父容器不对View有任何限制,要多大给多大,这般情况一般用于系统内部,表示一种测量状态,如ScrollView测量子View时用的就是这个。
EXACTLY
父容器已经检测出View所需要的大小,这个时候View的最终大小就是SpecSize所测定的值,它对应于LayoutParams中的match_parent和具体的数值(如40dp,60dp)这两种模式。
AT_MOST
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content.