Android 设置投影效果
16lz
2021-01-23
1. elevation设置投影
android api(限制 21 以上 ),不过我们还是无法自定义投影颜色,透明度,大小等
2. UI制作.9图片
3. paint.setShadowLayer(float radius, float dx, float dy, int shadowColor);
这个方法可以达到这样一个效果,在使用canvas画图时给视图顺带上一层阴影效果。
简单介绍一下这几个参数:
- radius: 阴影半径,主要可以控制阴影的模糊效果以及阴影扩散出去的大小。
- dx:阴影在X轴方向上的偏移量
- dy: 阴影在Y轴方向上的偏移量
- shadowColor: 阴影颜色
思路:
- 通过在父布局中绘制整个阴影背景,子布局为所要显示的View
- 给控件设置背景,在背景上做投影效果
3.1 自定义ShadowLayout
public class ShadowLayout extends FrameLayout { public static final String TAG = "ShadowLayout"; // 阴影颜色 private int mShadowColor; // 阴影范围大小 private float mShadowRadius; // 阴影圆角光滑度 private float mCornerRadius; // 阴影x轴偏移量 private float mDx; // 阴影y轴偏移量 private float mDy; private boolean mInvalidateShadowOnSizeChanged = true; private boolean mForceInvalidateShadow = false; public ShadowLayout(Context context) { super(context); initView(context, null); } public ShadowLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(context, attrs); } public ShadowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs); } @Override protected int getSuggestedMinimumWidth() { return 0; } @Override protected int getSuggestedMinimumHeight() { return 0; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (w > 0 && h > 0 && (getBackground() == null || mInvalidateShadowOnSizeChanged || mForceInvalidateShadow)) { mForceInvalidateShadow = false; setBackgroundCompat(w, h); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mForceInvalidateShadow) { mForceInvalidateShadow = false; setBackgroundCompat(right - left, bottom - top); } } public void setInvalidateShadowOnSizeChanged(boolean invalidateShadowOnSizeChanged) { mInvalidateShadowOnSizeChanged = invalidateShadowOnSizeChanged; } public void invalidateShadow() { mForceInvalidateShadow = true; requestLayout(); invalidate(); } private void initView(Context context, AttributeSet attrs) { initAttributes(context, attrs); /** x偏离量 **/ int xPadding = (int) (mShadowRadius + Math.abs(mDx)); /** y偏离量 **/ int yPadding = (int) (mShadowRadius + Math.abs(mDy)); setPadding(xPadding, yPadding, xPadding, yPadding); } private void initAttributes(Context context, AttributeSet attrs) { TypedArray attr = getTypedArray(context, attrs, R.styleable.ShadowLayout); if (attr == null) { return; } try { mCornerRadius = attr.getDimension(R.styleable.ShadowLayout_sl_cornerRadius, 0); mShadowRadius = attr.getDimension(R.styleable.ShadowLayout_sl_shadowRadius, 10); mDx = attr.getDimension(R.styleable.ShadowLayout_sl_dx, 0); mDy = attr.getDimension(R.styleable.ShadowLayout_sl_dy, 0); mShadowColor = attr.getColor(R.styleable.ShadowLayout_sl_shadowColor, Color.parseColor("#88757575")); } finally { attr.recycle(); } } @SuppressWarnings("deprecation") private void setBackgroundCompat(int w, int h) { Log.i(TAG, "setBackgroundCompat: w=" + w + ", h=" + h); Bitmap bitmap = createShadowBitmap(w, h, mCornerRadius, mShadowRadius, mDx, mDy, mShadowColor, Color.TRANSPARENT); BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) { setBackgroundDrawable(drawable); } else { setBackground(drawable); } } private TypedArray getTypedArray(Context context, AttributeSet attributeSet, int[] attr) { return context.obtainStyledAttributes(attributeSet, attr, 0, 0); } private Bitmap createShadowBitmap(int shadowWidth, int shadowHeight, float cornerRadius, float shadowRadius, float dx, float dy, int shadowColor, int fillColor) { Bitmap output = Bitmap.createBitmap(shadowWidth, shadowHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); RectF shadowRect = new RectF( shadowRadius, shadowRadius, shadowWidth - shadowRadius, shadowHeight - shadowRadius); if (dy > 0) { shadowRect.top += dy; shadowRect.bottom -= dy; } else if (dy < 0) { shadowRect.top += Math.abs(dy); shadowRect.bottom -= Math.abs(dy); } if (dx > 0) { shadowRect.left += dx; shadowRect.right -= dx; } else if (dx < 0) { shadowRect.left += Math.abs(dx); shadowRect.right -= Math.abs(dx); } Paint shadowPaint = new Paint(); shadowPaint.setAntiAlias(true); shadowPaint.setColor(fillColor); shadowPaint.setStyle(Paint.Style.FILL); if (!isInEditMode()) { // 阴影的区域默认在绘制区域,阴影移动均相对于绘制区域 shadowPaint.setShadowLayer(shadowRadius, dx, dy, shadowColor); } canvas.drawRoundRect(shadowRect, cornerRadius, cornerRadius, shadowPaint); return output; }}
attrs.xml
<declare-styleable name="ShadowLayout"> <attr name="sl_cornerRadius" format="dimension" /> <attr name="sl_shadowRadius" format="dimension" /> <attr name="sl_shadowColor" format="color" /> <attr name="sl_dx" format="dimension" /> <attr name="sl_dy" format="dimension" />declare-styleable>
例如要给TextView添加投影效果,使用如下
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/ll_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.base.module.customviewexer.ShadowLayout android:layout_width="200dp" android:layout_height="100dp" android:layout_centerInParent="true" app:sl_dy="5dp"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:gravity="center" android:text="hello world!" /> </com.base.module.customviewexer.ShadowLayout></RelativeLayout>
显示效果
3.2 自定义背景ShadowDrawable
public class ShadowDrawable extends Drawable { private Paint mShadowPaint; private Paint mBgPaint; private int mShadowRadius; private int mShape; private int mShapeRadius; private int mOffsetX; private int mOffsetY; private int mBgColor[]; private RectF mRect; public final static int SHAPE_ROUND = 1; public final static int SHAPE_CIRCLE = 2; private ShadowDrawable(int shape, int[] bgColor, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) { this.mShape = shape; this.mBgColor = bgColor; this.mShapeRadius = shapeRadius; this.mShadowRadius = shadowRadius; this.mOffsetX = offsetX; this.mOffsetY = offsetY; mShadowPaint = new Paint(); mShadowPaint.setColor(Color.TRANSPARENT); mShadowPaint.setAntiAlias(true); mShadowPaint.setShadowLayer(shadowRadius, offsetX, offsetY, shadowColor); mShadowPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); mBgPaint = new Paint(); mBgPaint.setAntiAlias(true); } @Override public void setBounds(int left, int top, int right, int bottom) { super.setBounds(left, top, right, bottom); mRect = new RectF(left + mShadowRadius - mOffsetX, top + mShadowRadius - mOffsetY, right - mShadowRadius - mOffsetX, bottom - mShadowRadius - mOffsetY); } @Override public void draw(@NonNull Canvas canvas) { if (mBgColor != null) { if (mBgColor.length == 1) { mBgPaint.setColor(mBgColor[0]); } else { mBgPaint.setShader(new LinearGradient(mRect.left, mRect.height() / 2, mRect.right, mRect.height() / 2, mBgColor, null, Shader.TileMode.CLAMP)); } } if (mShape == SHAPE_ROUND) { canvas.drawRoundRect(mRect, mShapeRadius, mShapeRadius, mShadowPaint); canvas.drawRoundRect(mRect, mShapeRadius, mShapeRadius, mBgPaint); } else { canvas.drawCircle(mRect.centerX(), mRect.centerY(), Math.min(mRect.width(), mRect.height()) / 2, mShadowPaint); canvas.drawCircle(mRect.centerX(), mRect.centerY(), Math.min(mRect.width(), mRect.height()) / 2, mBgPaint); } } @Override public void setAlpha(int alpha) { mShadowPaint.setAlpha(alpha); } @Override public void setColorFilter(@Nullable ColorFilter colorFilter) { mShadowPaint.setColorFilter(colorFilter); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } public static void setShadowDrawable(View view, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) { ShadowDrawable drawable = new ShadowDrawable.Builder() .setShapeRadius(shapeRadius) .setShadowColor(shadowColor) .setShadowRadius(shadowRadius) .setOffsetX(offsetX) .setOffsetY(offsetY) .builder(); view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); ViewCompat.setBackground(view, drawable); } public static void setShadowDrawable(View view, int bgColor, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) { ShadowDrawable drawable = new ShadowDrawable.Builder() .setBgColor(bgColor) .setShapeRadius(shapeRadius) .setShadowColor(shadowColor) .setShadowRadius(shadowRadius) .setOffsetX(offsetX) .setOffsetY(offsetY) .builder(); view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); ViewCompat.setBackground(view, drawable); } public static void setShadowDrawable(View view, int shape, int bgColor, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) { ShadowDrawable drawable = new ShadowDrawable.Builder() .setShape(shape) .setBgColor(bgColor) .setShapeRadius(shapeRadius) .setShadowColor(shadowColor) .setShadowRadius(shadowRadius) .setOffsetX(offsetX) .setOffsetY(offsetY) .builder(); view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); ViewCompat.setBackground(view, drawable); } public static void setShadowDrawable(View view, int[] bgColor, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) { ShadowDrawable drawable = new ShadowDrawable.Builder() .setBgColor(bgColor) .setShapeRadius(shapeRadius) .setShadowColor(shadowColor) .setShadowRadius(shadowRadius) .setOffsetX(offsetX) .setOffsetY(offsetY) .builder(); view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); ViewCompat.setBackground(view, drawable); } public static class Builder { private int mShape; private int mShapeRadius; private int mShadowColor; private int mShadowRadius; private int mOffsetX = 0; private int mOffsetY = 0; private int[] mBgColor; public Builder() { mShape = ShadowDrawable.SHAPE_ROUND; mShapeRadius = 12; mShadowColor = Color.parseColor("#4d000000"); mShadowRadius = 18; mOffsetX = 0; mOffsetY = 0; mBgColor = new int[1]; mBgColor[0] = Color.TRANSPARENT; } public Builder setShape(int mShape) { this.mShape = mShape; return this; } public Builder setShapeRadius(int ShapeRadius) { this.mShapeRadius = ShapeRadius; return this; } public Builder setShadowColor(int shadowColor) { this.mShadowColor = shadowColor; return this; } public Builder setShadowRadius(int shadowRadius) { this.mShadowRadius = shadowRadius; return this; } public Builder setOffsetX(int OffsetX) { this.mOffsetX = OffsetX; return this; } public Builder setOffsetY(int OffsetY) { this.mOffsetY = OffsetY; return this; } public Builder setBgColor(int BgColor) { this.mBgColor[0] = BgColor; return this; } public Builder setBgColor(int[] BgColor) { this.mBgColor = BgColor; return this; } public ShadowDrawable builder() { return new ShadowDrawable(mShape, mBgColor, mShapeRadius, mShadowColor, mShadowRadius, mOffsetX, mOffsetY); } }}
布局文件
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ll_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv_text" android:layout_width="200dp" android:layout_height="100dp" android:layout_centerInParent="true" android:background="#ffffff" android:gravity="center" android:text="hello world!" />RelativeLayout>
Activity中使用
public class MainActivity extends AppCompatActivity { private TextView mTvText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTvText = findViewById(R.id.tv_text); /* 1. 需要设置的view 2. 背景主题颜色 3. 背景弧度半径 4. shadow的颜色 5. shadow的扩散半径大小 6. shadow的x轴偏移 7. shadow的y周偏移*/ ShadowDrawable.setShadowDrawable(mTvText, Color.parseColor("#ffffff"), 10, Color.parseColor("#25000000"), 10, 0, 10); }}
实现效果
这种目前的限制是正常背景只能纯色,无法设置图片,选择器等,不过理解了原理,如果有需要也可以进行定制。
更多相关文章
- Android ListView滑动后背景变黑问题解决方法
- 在android中实现手势翻页效果
- [android]关于开发无触摸屏(智能电视)的android selector无法实
- Android实现左右滑动效果
- Android标题栏沉浸效果