在低版本中让按钮显示阴影

来源:互联网 发布:热敏标签打印软件 编辑:程序博客网 时间:2024/04/30 06:35

在一些情况需要让button显示阴影表示悬空的状态,在Android L 以上有 elevation属性可以使用,低版本就需要自己画阴影来表示悬浮状态。对于一个按钮一般只要支持圆角矩形就可以满足需求了(圆型按钮可以直接使用FloatingActionButton)。

  • 首先自定义View RoundRecButton 继承自Button:

    public class RoundRecButton extends Button
  • 对于自定义的RoundRecButton 添加两个自定义属性:

    <declare-styleable name="RoundRecButton">    <attr name="radius" format="dimension"/>    <attr name="background_tint" format="color"/></declare-styleable>

    这里background_tint 其实就是background 没有做tint的支持,不要被名字误导了(懒得改了)

  • 在View的3个构造方法中 做初始化工作,分别获取我们两个自定义属性的值,一个圆角的radius 和一个背景颜色(这里使用 colorStateList 是为了支持颜色选择器<selector/>

    private void init(Context context, AttributeSet attributeSet){    TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.RoundRecButton);    int radius = a.getDimensionPixelSize(R.styleable.RoundRecButton_radius, 0);    mStateList = a.getColorStateList(R.styleable.RoundRecButton_background_tint);    a.recycle();
  • 对于 Android L 及以上版本 使用elevation 设置:

    private boolean elevationSupported() {    return android.os.Build.VERSION.SDK_INT >= 21;}
    if (elevationSupported()) {        mCircle = new ShapeDrawable(new RoundRectShape(new float[]{radius,radius,radius,radius,radius,radius,radius,radius},null,null));        ViewCompat.setElevation(this, SHADOW_ELEVATION * density);    } 

    关于ShapeDrawable 就是对应于<shape android:shape="retangle"/>参数说明如下:

    /** * RoundRectShape constructor. * Specifies an outer (round)rect and an optional inner (round)rect. * * @param outerRadii An array of 8 radius values, for the outer roundrect.  *                   The first two floats are for the  *                   top-left corner (remaining pairs correspond clockwise).  *                   For no rounded corners on the outer rectangle,  *                   pass null. * @param inset      A RectF that specifies the distance from the inner  *                   rect to each side of the outer rect.  *                   For no inner, pass null. * @param innerRadii An array of 8 radius values, for the inner roundrect. *                   The first two floats are for the  *                   top-left corner (remaining pairs correspond clockwise).  *                   For no rounded corners on the inner rectangle,  *                   pass null. *                   If inset parameter is null, this parameter is ignored.  */public RoundRectShape(float[] outerRadii, RectF inset,                      float[] innerRadii) {
  • 对于Android L 以下版本使用自定义的RoundRectShadow具体就是在原RoundRecShape 的基础上在外侧绘制阴影:

    private class RoundRectShadow extends RoundRectShape{    private Paint mShadowPaint;    private RadialGradient mRadialGradient;    private RectF mRectF = new RectF();    private int mRadius;    public RoundRectShadow(float[] outerRadii, RectF inset, float[] innerRadii,int shadowRadius,int radius) {        super(outerRadii, inset, innerRadii);        mRadius = radius;        mShadowPaint = new Paint();        mShadowRadius = shadowRadius;        mRadialGradient = new RadialGradient(RoundRecButton.this.getWidth()/2, RoundRecButton.this.getHeight() / 2,                mShadowRadius, new int[] {                FILL_SHADOW_COLOR, Color.TRANSPARENT        }, null, Shader.TileMode.CLAMP);        mShadowPaint.setShader(mRadialGradient);    }    @Override    public void draw(Canvas canvas, Paint paint) {        final int width = RoundRecButton.this.getWidth();        final int height = RoundRecButton.this.getHeight();        mRectF.set(0,0,width,height);        canvas.drawRoundRect(mRectF, mRadius, mRadius, mShadowPaint);        mRectF.inset(mShadowRadius, mShadowRadius);        canvas.drawRoundRect(mRectF,mRadius,mRadius,paint);    }}

    在Android L 以下版本使用 带有阴影的自定义Shape:

    if(elevationSupported()){/.../}else {        RoundRectShape oval = new RoundRectShadow(new float[]{radius,radius,radius,radius,radius,radius,radius,radius}, null,null,mShadowRadius,radius);        mCircle = new ShapeDrawable(oval);        ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, mCircle.getPaint());        mCircle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,                KEY_SHADOW_COLOR);        final int padding = mShadowRadius;        // set padding so the inner image sits correctly within the shadow.        setPadding(padding, padding, padding, padding);    }

    使用ShadowLayer 会模糊边缘,因为在shape中只是单纯的绘制一层阴影。

  • 现在已经完成对阴影的支持,现在再加入对colorStateList的支持:
    设置初始颜色

    if(mStateList != null) {        mCircle.getPaint().setColor(mStateList.getColorForState(getDrawableState(),Color.WHITE));    }else {        mCircle.getPaint().setColor(Color.WHITE);    }

    当Drawable 状态改变时 设置对应的颜色并刷新界面

    @Overrideprotected void drawableStateChanged() {    super.drawableStateChanged();    if(mStateList != null){        mCircle.getPaint().setColor(mStateList.getColorForState(getDrawableState(),Color.WHITE));        invalidate();    }}
  • 之后再加入对RippleDrawable的支持(由于只是支持的颜色设置,所以这里都是需要自己用Drawable包装,其实可以稍作修改直接支持Drawable 就好了):

    if(Build.VERSION.SDK_INT >= 21){        TypedArray a1 = context.obtainStyledAttributes(new int[]{android.R.attr.colorControlHighlight});        ColorStateList colorStateList = a1.getColorStateList(0);        a1.recycle();        if(colorStateList != null) {            setBackground(new RippleDrawable(colorStateList, mCircle, null));        }    }else {        setBackgroundDrawable(mCircle);    }

    总结

    其实关键就是使用自定义的Shape进行阴影支持,如果直接进行setBackground,那就连状态选择及Ripple等都不用做处理了。源码如下:

    public class RoundRecButton extends Button {private static final int KEY_SHADOW_COLOR = 0x1E000000;private static final int FILL_SHADOW_COLOR = 0x3D000000;private static final float X_OFFSET = 0f;private static final float Y_OFFSET = 1.75f;private static final float SHADOW_RADIUS = 3.5f;private static final int SHADOW_ELEVATION = 4;private int mShadowRadius;private ColorStateList mStateList;private ShapeDrawable mCircle;public RoundRecButton(Context context) {    super(context);    init(context,null);}public RoundRecButton(Context context, AttributeSet attrs) {    super(context, attrs);    init(context,attrs);}public RoundRecButton(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    init(context,attrs);}@Overrideprotected void drawableStateChanged() {    super.drawableStateChanged();    if(mStateList != null){        mCircle.getPaint().setColor(mStateList.getColorForState(getDrawableState(),Color.WHITE));        invalidate();    }}private void init(Context context, AttributeSet attributeSet){    TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.RoundRecButton);    int radius = a.getDimensionPixelSize(R.styleable.RoundRecButton_radius, 0);    mStateList = a.getColorStateList(R.styleable.RoundRecButton_background_tint);    a.recycle();    final float density = getContext().getResources().getDisplayMetrics().density;    final int shadowYOffset = (int) (density * Y_OFFSET);    final int shadowXOffset = (int) (density * X_OFFSET);    mShadowRadius = (int) (density * SHADOW_RADIUS);    if (elevationSupported()) {        mCircle = new ShapeDrawable(new RoundRectShape(new float[]{radius,radius,radius,radius,radius,radius,radius,radius},null,null));        ViewCompat.setElevation(this, SHADOW_ELEVATION * density);    } else {        RoundRectShape oval = new RoundRectShadow(new float[]{radius,radius,radius,radius,radius,radius,radius,radius}, null,null,mShadowRadius,radius);        mCircle = new ShapeDrawable(oval);        ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, mCircle.getPaint());        mCircle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,                KEY_SHADOW_COLOR);        final int padding = mShadowRadius;        // set padding so the inner image sits correctly within the shadow.        setPadding(padding, padding, padding, padding);    }    if(mStateList != null) {        mCircle.getPaint().setColor(mStateList.getColorForState(getDrawableState(),Color.WHITE));    }else {        mCircle.getPaint().setColor(Color.WHITE);    }    if(Build.VERSION.SDK_INT >= 21){        TypedArray a1 = context.obtainStyledAttributes(new int[]{android.R.attr.colorControlHighlight});        ColorStateList colorStateList = a1.getColorStateList(0);        a1.recycle();        if(colorStateList != null) {            setBackground(new RippleDrawable(colorStateList, mCircle, null));        }    }else {        setBackgroundDrawable(mCircle);    }}@Overridepublic void setBackgroundResource(int resid) {    super.setBackgroundResource(resid);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    if (!elevationSupported()) {        setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight()                + mShadowRadius*2);    }}private boolean elevationSupported() {    return android.os.Build.VERSION.SDK_INT >= 21;}private class RoundRectShadow extends RoundRectShape{    private Paint mShadowPaint;    private RadialGradient mRadialGradient;    private RectF mRectF = new RectF();    private int mRadius;    public RoundRectShadow(float[] outerRadii, RectF inset, float[] innerRadii,int shadowRadius,int radius) {        super(outerRadii, inset, innerRadii);        mRadius = radius;        mShadowPaint = new Paint();        mShadowRadius = shadowRadius;        mRadialGradient = new RadialGradient(RoundRecButton.this.getWidth()/2, RoundRecButton.this.getHeight() / 2,                mShadowRadius, new int[] {                FILL_SHADOW_COLOR, Color.TRANSPARENT        }, null, Shader.TileMode.CLAMP);        mShadowPaint.setShader(mRadialGradient);    }    @Override    public void draw(Canvas canvas, Paint paint) {        final int width = RoundRecButton.this.getWidth();        final int height = RoundRecButton.this.getHeight();        mRectF.set(0,0,width,height);        canvas.drawRoundRect(mRectF, mRadius, mRadius, mShadowPaint);        mRectF.inset(mShadowRadius, mShadowRadius);        canvas.drawRoundRect(mRectF,mRadius,mRadius,paint);    }}}
0 0
原创粉丝点击