自定义View 仿闲鱼底部圆形摁扭,已开源(暂无动画)

前言:学了两天自定义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, "onDrawplus是否为空"+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, "onCilickplus是否为空: "+String.valueOf(plus == null));
        Log.i(TAG, String.valueOf(mPlusAnimator == null));
        mPlusAnimator.setTarget(plus);
        mPlusAnimator.start();


        Log.i("JSY", "setOnClickListener: " );
    }
}


效果图如上图一样,本人也是菜鸟一枚,不足的地方还请留言指

出,该控件已在github上开源,等特效bug修复后,会打包成

arr供大家食用

你可能感兴趣的:(自定义View,自定义View,Android)