diff --git a/library/src/main/java/com/ruffian/library/widget/helper/RBaseHelper.java b/library/src/main/java/com/ruffian/library/widget/helper/RBaseHelper.java index c0abe5b..4dbd80a 100644 --- a/library/src/main/java/com/ruffian/library/widget/helper/RBaseHelper.java +++ b/library/src/main/java/com/ruffian/library/widget/helper/RBaseHelper.java @@ -23,19 +23,20 @@ import android.view.ViewConfiguration; import android.view.ViewTreeObserver; -import androidx.annotation.ColorInt; -import androidx.annotation.StyleableRes; -import androidx.core.text.TextUtilsCompat; -import androidx.core.view.ViewCompat; - import com.ruffian.library.widget.R; import com.ruffian.library.widget.clip.ClipHelper; import com.ruffian.library.widget.clip.ClipPathManager; import com.ruffian.library.widget.clip.IClip; -import com.ruffian.library.widget.shadow.ShadowDrawable; +import com.ruffian.library.widget.shadow.ShadowBitmapDrawable; import java.util.Locale; +import androidx.annotation.ColorInt; +import androidx.annotation.RequiresApi; +import androidx.annotation.StyleableRes; +import androidx.core.text.TextUtilsCompat; +import androidx.core.view.ViewCompat; + /** * BaseHelper @@ -104,7 +105,7 @@ public class RBaseHelper implements IClip, ViewTreeObserver.OnGl private GradientDrawable.Orientation mGradientOrientation = GradientDrawable.Orientation.TOP_BOTTOM; //shadow - private ShadowDrawable mShadowDrawable; + private ShadowBitmapDrawable mShadowDrawable; private int mShadowDx; private int mShadowDy; private int mShadowColor; @@ -173,6 +174,7 @@ public RBaseHelper(Context context, T view, AttributeSet attrs) { addOnGlobalLayoutListener(); } + /** * 初始化控件属性 * @@ -278,7 +280,6 @@ private void setup() { mBackgroundSelected = new GradientDrawable(); mViewBackground = mView.getBackground(); mStateBackground = new StateListDrawable(); - if (useShadow()) mShadowDrawable = new ShadowDrawable(); //unable,focused,pressed,checked,selected,normal states[0] = new int[]{-android.R.attr.state_enabled};//unable states[1] = new int[]{android.R.attr.state_focused};//focused @@ -818,7 +819,6 @@ private void updateBackgroundValue() { setBackgroundState(); } - /** * 刷新StateListDrawable状态 * 更新drawable背景时时候刷新 @@ -874,16 +874,27 @@ private void setBackgroundState() { //获取drawable mBackgroundDrawable = getBackgroundDrawable(hasCustom, mRippleColor); if (useShadow()) { - mView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);//禁止硬件加速,must,导致绘制耗时(寻找优化路径) - if (mShadowDrawable == null) mShadowDrawable = new ShadowDrawable(); + + //1.早期实现方式禁止了硬件加速,并且由于 LAYER_TYPE_SOFTWARE 的特性导致在列表中卡顿(频繁创建缓存再渲染)(超过限制的view创建缓存过大导致无法展示背景) + //mView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);//禁止硬件加速,must,导致绘制耗时(寻找优化路径) + //if (mShadowDrawable == null) mShadowDrawable = new ShadowDrawable(); + //mShadowDrawable.updateParameter(mShadowColor, mShadowRadius, mShadowDx, mShadowDy, mBorderRadii); + //int shadowOffset = (int) mShadowDrawable.getShadowOffset(); + //int left = shadowOffset + Math.abs(mShadowDx); + //int right = shadowOffset + Math.abs(mShadowDx); + //int top = shadowOffset + Math.abs(mShadowDy); + //int bottom = shadowOffset + Math.abs(mShadowDy); + + //2.将阴影背景绘制成bitmap渲染,没有禁止硬件加速,和 LAYER_TYPE_SOFTWARE 的特性,大大提升性能。(在列表中大量使用阴影时,可能导致内存较高) + if (mShadowDrawable == null) mShadowDrawable = new ShadowBitmapDrawable(); mShadowDrawable.updateParameter(mShadowColor, mShadowRadius, mShadowDx, mShadowDy, mBorderRadii); - int shadowOffset = (int) mShadowDrawable.getShadowOffset(); - int left = shadowOffset + Math.abs(mShadowDx); - int right = shadowOffset + Math.abs(mShadowDx); - int top = shadowOffset + Math.abs(mShadowDy); - int bottom = shadowOffset + Math.abs(mShadowDy); + int left = mShadowRadius + Math.abs(mShadowDx); + int right = mShadowRadius + Math.abs(mShadowDx); + int top = mShadowRadius + Math.abs(mShadowDy); + int bottom = mShadowRadius + Math.abs(mShadowDy); + //设置背景 LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{mShadowDrawable, mBackgroundDrawable}); layerDrawable.setLayerInset(1, left, top, right, bottom);//设置第二层drawable四周偏移量 mBackgroundDrawable = layerDrawable; @@ -901,7 +912,6 @@ private void setBackgroundState() { } - /** * 获取 BackgroundDrawable * @@ -947,6 +957,7 @@ private Drawable getBackgroundDrawable(boolean hasCustom, int rippleColor) { * @param hasCustom 是否存在自定义背景 * @param rippleColor 水波纹颜色 */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private Object[] getRippleDrawableWithTag(boolean hasCustom, int rippleColor) { boolean isMaskNull;//是否不受限制的水波纹效果{ contentDrawable == null && maskDrawable == null } RippleDrawable rippleDrawable;//水波纹drawable @@ -1613,6 +1624,7 @@ public void onGlobalLayout() { initClip(); } + /** * 是否从右到左布局 * diff --git a/library/src/main/java/com/ruffian/library/widget/shadow/ShadowBitmapDrawable.java b/library/src/main/java/com/ruffian/library/widget/shadow/ShadowBitmapDrawable.java new file mode 100644 index 0000000..c706508 --- /dev/null +++ b/library/src/main/java/com/ruffian/library/widget/shadow/ShadowBitmapDrawable.java @@ -0,0 +1,147 @@ +package com.ruffian.library.widget.shadow; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; + +import com.ruffian.library.widget.utils.ReflectUtils; + +import java.util.Arrays; + +/** + * ShadowBitmapDrawable + * + * @author ZhongDaFeng + */ +public class ShadowBitmapDrawable extends BitmapDrawable { + + + private int mShadowColor;//阴影颜色 + private float mShadowRadius;//阴影半径 + private float[] mRoundRadii;//矩形圆角半径 + private int mShadowDx;//阴影x偏移 + private int mShadowDy;//阴影y偏移 + + private RectF mBoundsF; + + public ShadowBitmapDrawable() { + mBoundsF = new RectF(); + } + + /** + * RBaseHelp中属性修改时调用 + * + * @param shadowColor + * @param shadowRadius + * @param shadowDx + * @param shadowDy + * @param roundRadii + */ + public void updateParameter(int shadowColor, float shadowRadius, int shadowDx, int shadowDy, float[] roundRadii) { + boolean needUpdate = false; + if (mShadowColor != shadowColor || mShadowRadius != shadowRadius + || mShadowDx != shadowDx || mShadowDy != shadowDy + || !Arrays.equals(mRoundRadii, roundRadii)) { + needUpdate = true; + } + this.mShadowColor = shadowColor; + this.mRoundRadii = roundRadii; + this.mShadowRadius = shadowRadius; + this.mShadowDx = shadowDx; + this.mShadowDy = shadowDy; + + if (needUpdate) { + //重新创建bitmap + Bitmap bitmap = makeShadowBitmap((int) mBoundsF.width(), (int) mBoundsF.height(), mShadowRadius, mShadowDx, mShadowDy, mShadowColor, mRoundRadii); + + //反射调用函数 setBitmap 更新阴影图片 + ReflectUtils.invokeMethod(this, "setBitmap", new Class[]{Bitmap.class}, new Object[]{bitmap}); + } + + + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + if (bounds.right - bounds.left > 0 && bounds.bottom - bounds.top > 0) { + updateBounds(bounds); + } + } + + /** + * 更新Bounds + * + * @param bounds + */ + private void updateBounds(Rect bounds) { + if (bounds == null) bounds = getBounds(); + + float left = bounds.left + mShadowRadius + Math.abs(mShadowDx); + float right = bounds.right - mShadowRadius - Math.abs(mShadowDx); + float top = bounds.top + mShadowRadius + Math.abs(mShadowDy); + float bottom = bounds.bottom - mShadowRadius - Math.abs(mShadowDy); + + mBoundsF.set(left, top, right, bottom); + + //重新创建bitmap(不等高列表复用时可能会出现内存抖动,待优化) + Bitmap bitmap = makeShadowBitmap((int) mBoundsF.width(), (int) mBoundsF.height(), mShadowRadius, mShadowDx, mShadowDy, mShadowColor, mRoundRadii); + + //反射调用函数 setBitmap 更新阴影图片 + ReflectUtils.invokeMethod(this, "setBitmap", new Class[]{Bitmap.class}, new Object[]{bitmap}); + + } + + + /** + * 创建阴影Bitmap + * + * @param shadowWidth + * @param shadowHeight + * @param shadowRadius + * @param dx + * @param dy + * @param shadowColor + * @param radii + * @return + */ + public Bitmap makeShadowBitmap(int shadowWidth, int shadowHeight, float shadowRadius, + float dx, float dy, int shadowColor, float[] radii) { + + //容错处理 + if (shadowWidth <= 0 || shadowHeight <= 0) + return Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_4444); + + + //阴影bitmap + Bitmap output = Bitmap.createBitmap(shadowWidth, shadowHeight, Bitmap.Config.ARGB_4444); + Canvas canvas = new Canvas(output); + + RectF shadowRect = new RectF(shadowRadius, shadowRadius, shadowWidth - shadowRadius, shadowHeight - shadowRadius); + + + //存在偏移时重新计算阴影矩形大小 + shadowRect.top += Math.abs(dy); + shadowRect.bottom -= Math.abs(dy); + shadowRect.left += Math.abs(dx); + shadowRect.right -= Math.abs(dx); + + //阴影画笔 + Paint shadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + shadowPaint.setColor(shadowColor); + shadowPaint.setShadowLayer(shadowRadius, dx, dy, shadowColor); + + //路径 + Path path = new Path(); + path.addRoundRect(shadowRect, radii, Path.Direction.CW); + canvas.drawPath(path, shadowPaint); + + return output; + } + + +} diff --git a/library/src/main/java/com/ruffian/library/widget/utils/ReflectUtils.java b/library/src/main/java/com/ruffian/library/widget/utils/ReflectUtils.java new file mode 100644 index 0000000..f97af97 --- /dev/null +++ b/library/src/main/java/com/ruffian/library/widget/utils/ReflectUtils.java @@ -0,0 +1,63 @@ +package com.ruffian.library.widget.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * 反射工具类 + * + * @author ZhongDaFeng + */ +public class ReflectUtils { + + /** + * 循环向上转型, 获取对象的 DeclaredMethod + * + * @param object 子类对象 + * @param methodName 父类中的方法名 + * @param parameterTypes 父类中的方法参数类型 + * @return 父类中的方法对象 + */ + public static Method getDeclaredMethod(Object object, String methodName, Class... parameterTypes) { + Method method = null; + for (Class clazz = object.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) { + try { + method = clazz.getDeclaredMethod(methodName, parameterTypes); + return method; + } catch (Exception e) { + //这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。 + //如果这里的异常打印或者往外抛,则就不会执行clazz = clazz.getSuperclass(),最后就不会进入到父类中了 + } + } + return null; + } + + /** + * 直接调用对象方法, 而忽略修饰符(private, protected, default) + * + * @param object 子类对象 + * @param methodName 父类中的方法名 + * @param parameterTypes 父类中的方法参数类型 + * @param parameters 父类中的方法参数 + * @return 父类中方法的执行结果 + */ + public static Object invokeMethod(Object object, String methodName, Class[] parameterTypes, Object[] parameters) { + //根据 对象、方法名和对应的方法参数 通过反射 调用上面的方法获取 Method 对象 + Method method = getDeclaredMethod(object, methodName, parameterTypes); + try { + if (null != method) { + //抑制Java对方法进行检查,主要是针对私有方法而言 + method.setAccessible(true); + //调用object 的 method 所代表的方法,其方法的参数是 parameters + return method.invoke(object, parameters); + } + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return null; + } +}