1.创建一个包:weight
2.创建一个类:ClicleView extends View
四个构造方法
Context context 上下文引用
AttributeSet attrs 自定义属性集
int defStyleAttr 默认样式
int defStyleRes 默认资源引用
public SubView(Context context) {
super(context);
}
调用自身构造函数
public SubView(Context context) {
this(context, null);
}
//AttributeSet attrs 自定义属性集
//自定义属性从XML文件中进行获取
public SubView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SubView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
defStyleAttr Android默认样式
xmlns:android="http://schemas.android.com/apk/res/android" Android原生的命名空间集(删除,Android取值会取不到)
xmlns:day="http://schemas.android.com/apk/res-auto" Android命名空间
自定义View:
1、构造函数
2、设置XML中的命名空间
3、设置自定义属性
1.创建自定义属性:(在res/values/attrs 创建一个属性集)
2.声明自定义属性:
<resources>
<declare-styleable name="SubView">
<attr name="myText" format="string"></attr>
<attr name="myColor" format="color"></attr>
<attr name="myTextSize" format="dimension"></attr>
</declare-styleable>
</resources>
通过attr标签,声明属性值,并且规定接受类型
3.自定义属性取值:
①从res/values/attrs.xml中去取属性
②需要对属性声明名称
4.获取自定义属性集
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CircleView)
遍历自定义属性集,获取自定义属性(for循环遍历)
获取完毕后对自定义属性集中的下标内容进行获取
typedArray.getIndex(i);
4、对自定义控件进行设置
1.onLayout 控件位置的摆放(根据控件组摆放)
2.onMeasure 测量大小
3.onDraw 绘制出所显示的内容
onDraw(Canvas canvas){//cancas画板对象
//创建画笔对象
Paint paint = new Paint();
paint.setColor(mColor);//设置画笔颜色
canvas.drawCircle(0,0,mRadius,paint);//画圆
//参数:1.X轴坐标 2.Y轴坐标 3.圆的半径 4.画笔
}
setMeasuredDimesion();//根据控件的具体宽高进行设置
invalidate();//重绘方法
paint.setStyle(Paint.Style.STROKE);//设置画笔描边样式(空心圆)
自定义View绘制流程:
首先我们自定义view需要继承自android.View,能帮你处理android命名空间的属性,比如说android:layout_width=”match_parent”……,当然如果我们的控件需要自定的控件属性,则需要在attrs.xml文件中预定义样式格式,如下:
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyImageView">
<attr name="src" format="integer"/>
</declare-styleable>
</resources>
1
然后在使用过程中我们需要在layout文件中声明命名空间:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:img="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.sosky.skytalk.FriendFragment">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="85dp">
<!-- TODO: Update blank fragment layout -->
<com.example.sosky.skytalk.MyImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
img:src="@drawable/road"/>
</RelativeLayout>
</FrameLayout>
这样声明命令空间xmlns:img=”http://schemas.android.com/apk/res-auto”,还可以xmlns:img=”http://schemas.android.com/apk/res/包名”声明命名空间。
接下来我们就需要测量大小和绘制view,下面是我自定义的一个图片控件:
package com.example.sosky.skytalk;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.icu.util.Measure;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;
import java.util.jar.Attributes;
/**
* Created by sOSky on 2017/11/21.
*/
public class MyImageView extends View {
private Paint mBitmapPaint;
private Drawable mDrawable;
private Bitmap mBitmap;
private int mWidth;
private int mHeight;
public MyImageView(Context context){
this(context, null);
}
public MyImageView(Context context, AttributeSet attributeSet){
super(context,attributeSet);
initAtters(attributeSet);
mBitmapPaint = new Paint();
mBitmapPaint.setAntiAlias(true);
}
private void initAtters(AttributeSet attributeSet){
if (attributeSet != null) {
TypedArray array = null;
try {
array = getContext().obtainStyledAttributes(attributeSet,R.styleable.MyImageView);
mDrawable =array.getDrawable(R.styleable.MyImageView_src);
measureDrawable();
}finally {
if (array != null){
array.recycle();
}
}
}
}
private void measureDrawable(){
if (mDrawable == null) {
throw new RuntimeException("图片不存在!!!");
}
mWidth = mDrawable.getIntrinsicWidth();
mHeight = mDrawable.getIntrinsicWidth();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int widthmod = MeasureSpec.getMode(widthMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int heightmod = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(measureWidth(widthmod,width),measureHeight(heightmod,height));
}
@Override
protected void onDraw(Canvas canvas){
if (mBitmap == null){
mBitmap = Bitmap.createScaledBitmap(ImageUtils.drawableToBitmap(mDrawable),getMeasuredWidth(),getMeasuredHeight(),true);
}
canvas.drawBitmap(mBitmap,getLeft(),getTop(),mBitmapPaint);
}
private int measureWidth(int mod, int width){
switch(mod){
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
break;
case MeasureSpec.EXACTLY:
mWidth = width;
}
return mWidth;
}
private int measureHeight(int mod , int height){
switch(mod){
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
break;
case MeasureSpec.EXACTLY:
mHeight = height;
}
return mHeight;
}
}
1
这里主要关注的两点是,获取开发人员设置属性值和根据父视图测量自身大小。
获取属性值主要是通过Context.obtainStyledAttributes(attribueSet attrs,int styleableid);来获取开发人员设定的属性集中我们需要的属性,在这里我们需要的就是R.styleable.MyImageView。然后对获得的资源进行处理。
测量自身的大小我们需要复写onMeasure(int measurewidth,int measureheight );这两个参数是整个android View绘制的关键。
Measurespec是一个32位整形数据,前两位是测量模式(specMod),后30位父视图的窗口大小(specSize)。
三个测量模式分别是
MeasureSpec.EXACTLY:父视图希望子视图大小应该是由父视图窗口大小来决定,也可以设置成任意的大小。match_parent,具体的数值都是这个模式。
MeasureSpec.AT_MOST:表示子视图只能是specSize中指定的大小,不能超过specSize的大小,一般来说warp_context对应这个模式。
MeasureSpec.UNSPECIFIED:表示子视图能够设定为任意值。使用情况较少,系统开发会用到。
这样我们的子视图就能获取到父视图的窗口大小,再通过开发人员设置的layout属性,就能计算出view的大小。
我当时看到这里产生了一个疑问,这个measurespecwidth和Measurespecheight是从哪里来的。查了资料后,明白了其实是由一个ViewRootImpl类创建的,它是整个view绘制流程的控制类,所有view都是通过它绘制出来的。
根视图的MeasureSpec是由窗口大小和自身LayoutParams决定的,一般来说都是EXACTLY模式和窗口大小,然后他会调用
performMeasure(int childwidthSpec, int childheightSpec){
…..
mView.measure(childwidthSpec,childheightSpec)
……
}
可以看到它是调用的viewGroup的测量函数,遍历当中所有的子view,方式就是调用每一个view的onMeasure()函数。这样子view就获得了测绘模式,父视图大小和自身设定的layoutParams,通过这三个信息,就能计算出具体的大小。
网友评论