在低版本中让按钮显示阴影
来源:互联网 发布:热敏标签打印软件 编辑:程序博客网 时间: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); }}}
- 在低版本中让按钮显示阴影
- RadioButton在低版本中文字居中显示的问题
- RadioButton在低版本中文字居中显示的问题
- div让两个按钮并排显示在一行中
- 让IE6显示阴影
- 怎样让HTML5 CSS3网站在低版IE中显示
- 在xcode5中使用低版本sdk
- PyQT中让QMessageBox按钮显示中文
- 解决android 5.0中显示DatePicker为低版本
- 让低版本的 Android 项目显示出 Material 风格的点击效果
- 让低版本的 Android 项目显示出 Material 风格的点击效果
- Android 在低版本sdk中没有getSupportedPreviewFrameRates函数怎么办?
- 在Xcode中安装低版本的SDK和模拟器
- 在低版本Android中fragment会overlay ActionBar区域
- 怎么在低版本中使用MenuItem的setShowAsAction方法
- 在Xcode中安装低版本的SDK和模拟器
- 低版本jQuery在Firefox中运行不正确的解决
- 低版本中mysql不支持在limit语句中有子查询
- Time类解析
- Android的消息机制
- viewpager轮播图的实现(简洁易懂)
- Python用户存储加密及登录验证系统(乞丐版)
- IOS_函数
- 在低版本中让按钮显示阴影
- Android基本知识
- Linux上利用nginx域名转发
- ContentProvider学习笔记
- Linux命令积累_find
- 基于Deep Learning 的视频识别方法概览
- 计算机操作系统调度算法——短作业优先算法简单实现
- iOS发布新应用/更新新版本的流程
- array学习总结