在实际的开发过程中,Android系统自带的控件往往满足不了我们的需求,这就需要我们具备自定义控件的能力。一般来说,我们通常会有以下几种方法来实现:
1、继承原生控件进行扩展
2、组合原生几种控件
3、继承view或者viewGroup
一、view的绘制流程
在学习自定义view的时候,首先我们要搞清楚的就是Android view的绘制大致流程,了解相关函数的作用
View的绘制基本由measure()、layout()、draw()这个三个函数完成
第一步:OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。
第二步:OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。
第三步:OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制View的内容;④、绘制View子视图,如果没有就不用;
二、继承原生控件
这是最简单的实现方式,只需要继承Android原生控件,根据逻辑重写相关方法,就能得到一个全新的自定义控件
比如我们要实现一个圆角的imageview,只需要继承imageview,重写onDraw方法,在绘制的时候增加圆角处理即可。
三、组合原生控件
这种方式就是将Android原生的几个控件组合在一起,成为一个全新的控件
比如我们将两个button和一个editText组合在一起,通过button控制editText里面的数字增减。
1、首先自定义属性和自定义布局
style.xml
<declare-styleable name="MyLayout"> <!-- button的背景色 --> <attr name="bgButton" format="color"></attr> <!-- 输入框的文字字号 --> <attr name="sizeEdit" format="dimension"></attr> </declare-styleable>
view_my.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/add_button" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2" android:text="+" android:textStyle="bold" android:gravity="center" android:textSize="14sp" android:textColor="#ffffff" /> <EditText android:id="@+id/content_edit" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:enabled="false" android:background="@null" android:layout_marginLeft="10dp" android:text="0" android:gravity="center"/> <Button android:id="@+id/del_button" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2" android:text="-" android:textStyle="bold" android:gravity="center" android:textSize="14sp" android:layout_marginLeft="10dp" android:textColor="#ffffff" /> </LinearLayout>
2、加载自定义布局和定义的属性
LayoutInflater.from(context).inflate(R.layout.view_my,this); mAddBtn=findViewById(R.id.add_button); mAddBtn.setOnClickListener(this); mDelBtn=findViewById(R.id.del_button); mDelBtn.setOnClickListener(this); mContentET=findViewById(R.id.content_edit); TypedArray obtainStyledAttributes = context.obtainStyledAttributes(attrs, R.styleable.MyLayout); int bgColor=obtainStyledAttributes.getColor(R.styleable.MyLayout_bgButton,0xffff00); int fontSize=obtainStyledAttributes.getDimensionPixelSize(R.styleable.MyLayout_sizeEdit,12); mAddBtn.setBackgroundColor(bgColor); mDelBtn.setBackgroundColor(bgColor); mContentET.setTextSize(fontSize); obtainStyledAttributes.recycle();
四、继承view或者viewGroup
1、view和viewGroup的区别
可能有些人还不知道他们之间的区别吧,我们平时开发不会直接使用到这两个类,但是我们的UI界面其实都是由这两个类构成的,只是使用的都是他们派生出来的,比如TextView继承的view,RelativeLayout继承的viewGroup
viewGroup是容纳view的容器,本身也是继承的view,所以viewgroup里面容纳了很多的child也就是view,所以在viewGroup绘制的时候也就是绘制他的child
2、我们来实现一个简单的自定义view,实现一个圆点指示器的效果
自定义属性 style.xml
<declare-styleable name="MyView"> <!-- 被选中圆点的颜色 --> <attr name="fillColor" format="color" /> <!-- 未选中圆点的颜色 --> <attr name="strokeColor" format="color" /> <!-- 圆点的大小 --> <attr name="radius" format="dimension" /> <!-- 圆点间间距的大小 --> <attr name="circleInterval" format="dimension" /> </declare-styleable>
继承view,重写onMeasure、onDraw方法,计算宽高和绘制相关效果
public class MyView extends View { private float radius=2;//圆点半径 private Paint mFillPaint;//当前圆点的画笔 private Paint mStokePaint;//其他圆点的画笔 private int fillColor;//当前圆点的颜色值 private int stokeColor;//其他圆点的颜色值 private float circleMargin;//圆点直接的间隔 private int count;//圆点总个数 private int current=0;//当前圆点下标值 public MyView(Context context) { super(context); } public MyView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context,attrs); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context,attrs); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context,attrs); } private void init(Context context, @Nullable AttributeSet attrs){ TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.MyView); fillColor=typedArray.getColor(R.styleable.MyView_fillColor,0x000000); stokeColor=typedArray.getColor(R.styleable.MyView_strokeColor,0xffffff); circleMargin=typedArray.getDimension(R.styleable.MyView_circleInterval,2); radius=typedArray.getDimension(R.styleable.MyView_radius,2); mFillPaint=new Paint(Paint.ANTI_ALIAS_FLAG); mFillPaint.setColor(fillColor); mFillPaint.setStyle(Paint.Style.FILL); mStokePaint=new Paint(Paint.ANTI_ALIAS_FLAG); mStokePaint.setColor(stokeColor); mStokePaint.setStyle(Paint.Style.STROKE); typedArray.recycle(); } public void setCount(int count){ this.count=count; requestLayout(); } public void setCurrent(int current){ this.current=current; invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode=MeasureSpec.getMode(widthMeasureSpec); int widthSize=MeasureSpec.getSize(widthMeasureSpec); int heightMode=MeasureSpec.getMode(heightMeasureSpec); int heightSize=MeasureSpec.getSize(heightMeasureSpec); int width=0; int height=0; //计算宽度 if (widthMode==MeasureSpec.EXACTLY){//确切模式 width=widthSize; }else {//wrap_content float result=getPaddingLeft()+getPaddingRight()+count*2*radius+(count-1)*circleMargin; width=Math.min((int)result,widthSize); } //计算高度 if (heightMode==MeasureSpec.EXACTLY){//确切模式 height=heightSize; }else {//wrap_content float result=getPaddingTop()+getPaddingBottom()+2*radius; height=Math.min((int)result,heightSize); } setMeasuredDimension(width,height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int with=getMeasuredWidth(); float result=getPaddingLeft()+getPaddingRight()+count*2*radius+(count-1)*circleMargin; if (with>result) { float startX = (canvas.getWidth() - result) / 2; if (startX > 0) { canvas.translate(startX, 0); } } for (int i=0;i<count;i++){ if (i==current){//画当前点 float cx=(radius+getPaddingLeft()+(2*radius+circleMargin)*i); canvas.drawCircle(cx,getPaddingTop()+radius,radius,mFillPaint); }else { float cx=(radius+getPaddingLeft()+(2*radius+circleMargin)*i); canvas.drawCircle(cx,getPaddingTop()+radius,radius,mStokePaint); } } } }
使用自定义view
<com.test.find.MyView android:id="@+id/myView2" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/myView" android:layout_marginTop="10dp" app:fillColor="#ff0000" app:strokeColor="#000000" app:radius="4dp" app:circleInterval="5dp" />