前言:学了两天自定义View兴致比较高,之前学习都比较片面,这几天学习的比较系统,也明白了很多东西例如自定义View的整体流程,自定义View要是用的一些类 ,比如Paint呀,TypedArray呀,MeasureSpec等等,都有了个初步了解,后续我也会在工作中通过文档补充更详细的知识点,为了巩固知识,就做了个闲鱼底部的菜单栏,突然就发现这种菜单栏也是越来越流行了呀,就做了一个,下面就是整体流程。
开源链接 : https://github.com/Nixo0427/NixoBottomButton
效果图:
本文整体分位4个部分去说这个控件,最后会放出自定义控件类的整体代码
一、在attrs里声明需要的属性
二、获取属性值
三、测量大小
四、绘制
一、声明属性
首先在values里创建一个attrs.xml(名字可以随意起)
然后再里面去声明自己想要使用的属性,首先我们需要对比图片分析一下这个控件需要哪些值,思考过后,我们得出大概会的出控件大概会有以下属性
1.背景
2.文本
3.文本大小
4.圆距离底部的距离
5.圆的边框
6.圆的边框颜色
7.圆的颜色
8.圆的半径
那么我们就在里面声明这8个属性
xml version="1.0" encoding="utf-8"?>name="NixoBottomButton"> name="android:background"/> name="android:text"/> name="textSize" format="integer"/> name="isClick" format="boolean"/> name="circlePaddingBottom" format="integer"/> name="circleFrame" format="integer"/> name="circleFrameColor" format="color"/> name="circleColor" format="color"/> name="circleRadis" format="integer"/>
这样我们第一步就完成了
二、获取属性值
我们定义好属性,我们就需要获取值方便做后面的操作,比如绘制,我们新建一个类并继承Button
接下来声明一个TyedArray去获取属性,代码如下
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.NixoBottomButton); mTextSize = (int)typedArray.getFloat(R.styleable.NixoBottomButton_textSize,30); // mText = typedArray.getString(R.styleable.NixoBottomButton_android_text); mIsClick = typedArray.getBoolean(R.styleable.NixoBottomButton_isClick,false); mCircleColor = typedArray.getColor(R.styleable.NixoBottomButton_circleColor, Color.parseColor("#a3cf62")); mBackground = typedArray.getColor(R.styleable.NixoBottomButton_android_background, Color.parseColor("#00000000")); mPaddingBottom = typedArray.getInt(R.styleable.NixoBottomButton_circlePaddingBottom,20); mRaids = typedArray.getInt(R.styleable.NixoProgessBar_CircleRaids,0); mCircleFrameColor = typedArray.getColor(R.styleable.NixoBottomButton_circleFrameColor,Color.WHITE); mCircleFrame = typedArray.getInt(R.styleable.NixoBottomButton_circleFrame,5); mPlusAnimator = AnimatorInflater.loadAnimator(context,R.animator.plus_animator); typedArray.recycle();
最后不要忘记使用.recycle()方法进行回收,可能有的朋友发现了,圆的中心是有个加号的,这个属性并没有声明,的确是这样,但是不要紧,我们可以对外暴露个方法,让用户设置这个Icon,所以我们声明个int型的mIcon,然后通过如下代码转换成Bitmap
public void setIcon(int icon){ mIcon = icon; } public int getIcon(){ return mIcon; }
plus = ((BitmapDrawable) getContext().getResources().getDrawable(mIcon)).getBitmap();
这样,我们的属性获取就算告一段落了。
三、测量大小
测量大小是模板的写法,我们复写onMeasure方法,通过他给我们的widthMeasureSpec和heightMeasureSpec进行我们的模式对比,去设置不同的大小
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int SpecWidth = MeasureSpec.getSize(widthMeasureSpec); int SpecWidthMode = MeasureSpec.getMode(widthMeasureSpec); int SpecHeight = MeasureSpec.getSize(heightMeasureSpec); int SpecHeightMode = MeasureSpec.getMode(heightMeasureSpec); int width = 0; int height = 0; if(SpecWidthMode == MeasureSpec.EXACTLY){ width = SpecWidth; }else{ int needWidth = MeasureWidth() + getPaddingLeft() + getPaddingRight(); if(SpecWidthMode == MeasureSpec.AT_MOST){
//AT_MOST 就是不能大于父控件(Warp_content),所以这里把设置的大小和父控件的大小去比较,取最小值width = Math. min( needWidth , width) ; } else{
//这里的Mode是 UNSPECIFIED(不限定宽高) // 所以是测量的是多大,他就显示多大.width = needWidth; } } if( SpecHeightMode == MeasureSpec. EXACTLY){ height = ( int) ( SpecHeight + width/ 2) ; } else{ int needHeight = MeasureHeight() + getPaddingBottom() + getPaddingEnd() ; if( SpecHeightMode == MeasureSpec. AT_MOST){ height = Math. min( needHeight , height) ; } else{ height = needHeight ; } } super.onMeasure( widthMeasureSpec , heightMeasureSpec) ; }
private int MeasureHeight() { //如果用户设置Warp_content的话,我们就可以使用高度+圆距离底部的距离作为高度 return getHeight() + mPaddingBottom; } private int MeasureWidth() { //同上,我们可以使用宽度+边框作为宽度 return getWidth() + mCircleFrame; }
测量大小是模板式的写法,可以理解着记一下,我们的测量大小也就到此结束了。
四、绘制
我们通过复写onDraw来绘制我们的Button,首先我们需要声明一个Paint代码如下
private void initPaint() { paint = new Paint(); paint.setColor(Color.parseColor("#FFB6C1")); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(6); }
随后,我们需要绘制一个实心圆,这个圆的圆心的横坐标应该是Button宽度的一半,纵坐标为高度一半+距离底部的距离,但是要注意,半径我们要减去外边框的的长度代码如下:
mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mCircleColor); canvas.drawCircle(getWidth()/2 , getHeight()/2 - mPaddingBottom , mRaids , mPaint);
然后需要绘制我们的圆外的那个边框,(圆心坐标跟上述实心圆一样)我们将Paint设置为Stroke,并设置进去我们获得的宽度然后画圆代码如下
mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(mCircleFrameColor); mPaint.setStrokeWidth(mCircleFrame); canvas.drawCircle(getWidth()/2,getHeight()/2 - mPaddingBottom,mRaids + mCircleFrame - mPaint.getStrokeWidth(), mPaint);
然后绘制我们的ICON(x应该是我们button的中心,y应该是我们圆的中心)代码如下
canvas.drawBitmap(plus, (getWidth()-plus.getWidth())/2, mRaids, mPaint);
最后,我们把文本绘制上去 (x还是button的中心,y是button高度-圆距离底部的距离)代码如下
mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(0); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.setTextSize(mTextSize); mPaint.setColor(Color.BLACK); canvas.drawText(mText,getWidth()/2,getHeight()-(mPaddingBottom),mPaint);
到这里,整个绘制就结束了。我们可以使用这个控件来看一看我
们的结果。
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:id="@+id/BottomButton"
android:layout_width="100dp"
android:layout_height="120dp"
android:background="#00000000"
Nixo:circleColor="#6475fd"
Nixo:textSize="30"
Nixo:circleFrameColor="#fff"
Nixo:circlePaddingBottom="7"
Nixo:circleFrame="14"
/>
总体代码
package com.example.nixo.nixoview; import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.widget.Button; public class NixoBottomButton extends android.support.v7.widget.AppCompatButton { private boolean mIsClick; private int mCircleColor; private int mPaddingBottom; private Paint mPaint; private int mRaids; private int mCircleFrameColor; private int mCircleFrame; private int mBackground; private int mIcon; private Bitmap plus; private Animator mPlusAnimator; private static final String TAG = "JSY"; private String mText = "工具栏"; private int mTextSize; public NixoBottomButton(Context context) { this(context,null); } public NixoBottomButton(Context context, AttributeSet attrs) { super(context, attrs); init(context,attrs); } private void init(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.NixoBottomButton); mTextSize = (int)typedArray.getFloat(R.styleable.NixoBottomButton_textSize,30); // mText = typedArray.getString(R.styleable.NixoBottomButton_android_text); mIsClick = typedArray.getBoolean(R.styleable.NixoBottomButton_isClick,false); mCircleColor = typedArray.getColor(R.styleable.NixoBottomButton_circleColor, Color.parseColor("#a3cf62")); mBackground = typedArray.getColor(R.styleable.NixoBottomButton_android_background, Color.parseColor("#00000000")); mPaddingBottom = typedArray.getInt(R.styleable.NixoBottomButton_circlePaddingBottom,20); mRaids = typedArray.getInt(R.styleable.NixoProgessBar_CircleRaids,0); mCircleFrameColor = typedArray.getColor(R.styleable.NixoBottomButton_circleFrameColor,Color.WHITE); mCircleFrame = typedArray.getInt(R.styleable.NixoBottomButton_circleFrame,5); mPlusAnimator = AnimatorInflater.loadAnimator(context,R.animator.plus_animator); typedArray.recycle(); mIcon = R.mipmap.plus; plus = ((BitmapDrawable) getContext().getResources().getDrawable(mIcon)).getBitmap(); Log.i(TAG, String.valueOf(mPlusAnimator == null)); initPaint(); } private void initPaint() { mPaint = new Paint(); mPaint.setDither(true); mPaint.setAntiAlias(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int SpecWidth = MeasureSpec.getSize(widthMeasureSpec); int SpecWidthMode = MeasureSpec.getMode(widthMeasureSpec); int SpecHeight = MeasureSpec.getSize(heightMeasureSpec); int SpecHeightMode = MeasureSpec.getMode(heightMeasureSpec); int width = 0; int height = 0; if(SpecWidthMode == MeasureSpec.EXACTLY){ width = SpecWidth; }else{ int needWidth = MeasureWidth() + getPaddingLeft() + getPaddingRight(); if(SpecWidthMode == MeasureSpec.AT_MOST){ width = Math.min(needWidth,width); }else{ width = needWidth; } } if(SpecHeightMode == MeasureSpec.EXACTLY){ height = (int) (SpecHeight + width/2); }else{ int needHeight = MeasureHeight() + getPaddingBottom() + getPaddingEnd(); if(SpecHeightMode == MeasureSpec.AT_MOST){ height = Math.min(needHeight,height); }else{ height = needHeight; } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } private int MeasureHeight() { //如果用户设置Warp_content的话,我们就可以使用高度+圆距离底部的距离作为高度 return getHeight() + mPaddingBottom; } private int MeasureWidth() { //同上,我们可以使用宽度+边框作为宽度 return getWidth() + mCircleFrame; } public void setIcon(int icon){ mIcon = icon; } public int getIcon(){ return mIcon; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mRaids = getWidth()/2 - mCircleFrame; Log.i("Nixo", ""+mRaids); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mCircleColor); canvas.drawCircle(getWidth()/2 , getHeight()/2 - mPaddingBottom , mRaids , mPaint); //获取背景图片 Log.i(TAG, "onDraw的plus是否为空"+String.valueOf(plus == null)); canvas.drawBitmap(plus, (getWidth()-plus.getWidth())/2, mRaids, mPaint); //画背景图片 mPaint.setColor(mBackground); canvas.drawRect(0,0 ,getWidth(),getHeight()/2,mPaint); mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(mCircleFrameColor); mPaint.setStrokeWidth(mCircleFrame); canvas.drawCircle(getWidth()/2,getHeight()/2 - mPaddingBottom,mRaids + mCircleFrame - mPaint.getStrokeWidth(), mPaint); mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(0); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.setTextSize(mTextSize); mPaint.setColor(Color.BLACK); canvas.drawText(mText,getWidth()/2,getHeight()-(mPaddingBottom),mPaint); } private static final String BACKGROUND_COLOR = "background_color"; private static final String SUPER = "super"; @Override public Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putInt(BACKGROUND_COLOR,mBackground); bundle.putParcelable(SUPER,super.onSaveInstanceState()); return bundle; } @Override public void onRestoreInstanceState(Parcelable state) { if(state instanceof Bundle) { Bundle bundle = (Bundle) state; mBackground = bundle.getInt(BACKGROUND_COLOR); Parcelable parcelable = bundle.getParcelable(SUPER); super.onRestoreInstanceState(parcelable); return; } super.onRestoreInstanceState(state); } public void setPlusAnimator(ObjectAnimator mPlusAnimator) { this.mPlusAnimator = mPlusAnimator; } @Override public void setOnClickListener(@Nullable OnClickListener l) { super.setOnClickListener(l); Log.i(TAG, "onCilick的plus是否为空: "+String.valueOf(plus == null)); Log.i(TAG, String.valueOf(mPlusAnimator == null)); mPlusAnimator.setTarget(plus); mPlusAnimator.start(); Log.i("JSY", "setOnClickListener: " ); } }
效果图如上图一样,本人也是菜鸟一枚,不足的地方还请留言指
出,该控件已在github上开源,等特效bug修复后,会打包成
arr供大家食用