如何为 Pre-lollipop 实现 Material-design Elevation
Posted
技术标签:
【中文标题】如何为 Pre-lollipop 实现 Material-design Elevation【英文标题】:How to implement the Material-design Elevation for Pre-lollipop 【发布时间】:2015-08-26 23:52:52 【问题描述】:Google 已经展示了一些很好的方法,可以在 Lollipop here 上显示高程效果。
android:elevation="2dp"
对于按钮,
android:stateListAnimator="@anim/button_state_list_animator"
如何在没有 3rd 方库的情况下模拟棒棒糖前版本的提升效果?
【问题讨论】:
您可以使用应用程序中的自定义库来做到这一点。您可以在以下链接中找到示例:github.com/wasabeef/awesome-android-ui @RajanBhavsar 我可以在没有 3rd 方库的情况下实现吗? 是的,arjunu.com/2015/02/materializing-your-apps-for-pre-lollipop 请点击此链接。 你可以试试这个:***.com/questions/26728570/… Android view shadow的可能重复 【参考方案1】:添加@Ranjith Kumar 回答
要将背景颜色添加到可绘制对象(示例按钮背景颜色),我们需要以编程方式获取可绘制对象。
首先获取可绘制对象
Drawable drawable = getResources().getDrawable(android.R.drawable.dialog_holo_light_frame);
设置颜色
drawable.setColorFilter(new PorterDuffColorFilter(getResources().getColor(R.color.color_primary), PorterDuff.Mode.MULTIPLY));
然后将其设置为视图。
view.setBackgroundDrawable(drawable);
以防有人搜索。
【讨论】:
【参考方案2】:你可以用官方方法模仿棒棒糖之前的海拔高度。
我实现了同样的效果,
android:background="@android:drawable/dialog_holo_light_frame"
我的测试输出:
参考 - https://***.com/a/25683148/3879847
感谢用户@Repo..
更新:如果你想改变这个drawable的颜色,试试下面的@Irfan回答↓
https://***.com/a/40815944/3879847
【讨论】:
如果我想使用蓝色背景的按钮怎么办 如何使用此方法自定义海拔值【参考方案3】:要为棒棒糖之前的设备带来动态的动画阴影,您必须:
-
将视图的黑色形状绘制到位图
使用高程作为半径模糊该形状。您可以使用 RenderScript 来做到这一点。这与 Lollipop 使用的方法不完全一样,但效果很好,而且很容易添加到现有视图中。
在视图下方绘制那个模糊的形状。可能最好的地方是
drawChild
方法。
您还必须覆盖 setElevation
和 setTranslationZ
,覆盖布局中的子视图绘制,关闭 clip-to-padding 并实现状态动画。
这是一项繁重的工作,但它提供了最好看的动态阴影和响应动画。我不确定您为什么要在没有第三方库的情况下实现这一目标。如果您愿意,您可以分析 Carbon 的来源并移植您希望在您的应用中拥有的部分:
Shadow generation
private static void blurRenderScript(Bitmap bitmap, float radius)
Allocation inAllocation = Allocation.createFromBitmap(renderScript, bitmap,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
Allocation outAllocation = Allocation.createTyped(renderScript, inAllocation.getType());
blurShader.setRadius(radius);
blurShader.setInput(inAllocation);
blurShader.forEach(outAllocation);
outAllocation.copyTo(bitmap);
public static Shadow generateShadow(View view, float elevation)
if (!software && renderScript == null)
try
renderScript = RenderScript.create(view.getContext());
blurShader = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
catch (RSRuntimeException ignore)
software = true;
ShadowView shadowView = (ShadowView) view;
CornerView cornerView = (CornerView) view;
boolean isRect = shadowView.getShadowShape() == ShadowShape.RECT ||
shadowView.getShadowShape() == ShadowShape.ROUND_RECT && cornerView.getCornerRadius() < view.getContext().getResources().getDimension(R.dimen.carbon_1dip) * 2.5;
int e = (int) Math.ceil(elevation);
Bitmap bitmap;
if (isRect)
bitmap = Bitmap.createBitmap(e * 4 + 1, e * 4 + 1, Bitmap.Config.ARGB_8888);
Canvas shadowCanvas = new Canvas(bitmap);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xff000000);
shadowCanvas.drawRect(e, e, e * 3 + 1, e * 3 + 1, paint);
blur(bitmap, elevation);
return new NinePatchShadow(bitmap, elevation);
else
bitmap = Bitmap.createBitmap((int) (view.getWidth() / SHADOW_SCALE + e * 2), (int) (view.getHeight() / SHADOW_SCALE + e * 2), Bitmap.Config.ARGB_8888);
Canvas shadowCanvas = new Canvas(bitmap);
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xff000000);
if (shadowView.getShadowShape() == ShadowShape.ROUND_RECT)
roundRect.set(e, e, (int) (view.getWidth() / SHADOW_SCALE - e), (int) (view.getHeight() / SHADOW_SCALE - e));
shadowCanvas.drawRoundRect(roundRect, e, e, paint);
else
int r = (int) (view.getWidth() / 2 / SHADOW_SCALE);
shadowCanvas.drawCircle(r + e, r + e, r, paint);
blur(bitmap, elevation);
return new Shadow(bitmap, elevation);
Drawing a view with a shadow
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime)
if (!child.isShown())
return super.drawChild(canvas, child, drawingTime);
if (!isInEditMode() && child instanceof ShadowView && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH)
ShadowView shadowView = (ShadowView) child;
Shadow shadow = shadowView.getShadow();
if (shadow != null)
paint.setAlpha((int) (ShadowGenerator.ALPHA * ViewHelper.getAlpha(child)));
float childElevation = shadowView.getElevation() + shadowView.getTranslationZ();
float[] childLocation = new float[](child.getLeft() + child.getRight()) / 2, (child.getTop() + child.getBottom()) / 2;
Matrix matrix = carbon.internal.ViewHelper.getMatrix(child);
matrix.mapPoints(childLocation);
int[] location = new int[2];
getLocationOnScreen(location);
float x = childLocation[0] + location[0];
float y = childLocation[1] + location[1];
x -= getRootView().getWidth() / 2;
y += getRootView().getHeight() / 2; // looks nice
float length = (float) Math.sqrt(x * x + y * y);
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(
x / length * childElevation / 2,
y / length * childElevation / 2);
canvas.translate(
child.getLeft(),
child.getTop());
canvas.concat(matrix);
canvas.scale(ShadowGenerator.SHADOW_SCALE, ShadowGenerator.SHADOW_SCALE);
shadow.draw(canvas, child, paint);
canvas.restoreToCount(saveCount);
if (child instanceof RippleView)
RippleView rippleView = (RippleView) child;
RippleDrawable rippleDrawable = rippleView.getRippleDrawable();
if (rippleDrawable != null && rippleDrawable.getStyle() == RippleDrawable.Style.Borderless)
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(
child.getLeft(),
child.getTop());
rippleDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
return super.drawChild(canvas, child, drawingTime);
Elevation API backported to pre-Lollipop
private float elevation = 0;
private float translationZ = 0;
private Shadow shadow;
@Override
public float getElevation()
return elevation;
public synchronized void setElevation(float elevation)
if (elevation == this.elevation)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
super.setElevation(elevation);
this.elevation = elevation;
if (getParent() != null)
((View) getParent()).postInvalidate();
@Override
public float getTranslationZ()
return translationZ;
public synchronized void setTranslationZ(float translationZ)
if (translationZ == this.translationZ)
return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
super.setTranslationZ(translationZ);
this.translationZ = translationZ;
if (getParent() != null)
((View) getParent()).postInvalidate();
@Override
public ShadowShape getShadowShape()
if (cornerRadius == getWidth() / 2 && getWidth() == getHeight())
return ShadowShape.CIRCLE;
if (cornerRadius > 0)
return ShadowShape.ROUND_RECT;
return ShadowShape.RECT;
@Override
public void setEnabled(boolean enabled)
super.setEnabled(enabled);
setTranslationZ(enabled ? 0 : -elevation);
@Override
public Shadow getShadow()
float elevation = getElevation() + getTranslationZ();
if (elevation >= 0.01f && getWidth() > 0 && getHeight() > 0)
if (shadow == null || shadow.elevation != elevation)
shadow = ShadowGenerator.generateShadow(this, elevation);
return shadow;
return null;
@Override
public void invalidateShadow()
shadow = null;
if (getParent() != null && getParent() instanceof View)
((View) getParent()).postInvalidate();
【讨论】:
指向文件行的 Elevation API 链接从今天开始在一天前更新。所以,你能不能提一点代码,所以,开发人员对实际代码有更好的了解。【参考方案4】:你可以通过像这样声明一个drawable来轻松模拟它 -
shadow.xml
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
>
<gradient android:type="linear" android:angle="270" android:startColor="#b6b6b6" android:endColor="#ffffff"/>
</shape>
并在你的主要 xml 中使用它 -
android:background="@drawable/shadow"
【讨论】:
【参考方案5】:您可以使用卡片视图破解它:
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/btnGetStuff"
android:layout_
android:layout_
card_view:cardCornerRadius="4dp"
card_view:cardBackgroundColor="@color/accent"
>
<!-- you could also add image view here for icon etc. -->
<TextView
android:id="@+id/txtGetStuff"
android:layout_
android:layout_
android:textSize="@dimen/textSize_small"
android:textColor="@color/primary_light"
android:freezesText="true"
android:text="Get Stuff"
android:maxWidth="120dp"
android:singleLine="true"
android:ellipsize="end"
android:maxLines="1"
/></android.support.v7.widget.CardView>
或者看看使用这个第三方库:https://github.com/rey5137/Material(参见关于按钮 https://github.com/rey5137/Material/wiki/Button 的 wiki 文章)
【讨论】:
【参考方案6】:创建一个9-patch 图像,并在图像上定义可拉伸的补丁,并在其周围有阴影。
将此 9-patch 图像添加为按钮的背景,并使用填充物使阴影可见。
您可以找到一些预定义的 9-patch (.9.png) 图像 here 或 here,您可以从中选择、自定义和复制到项目的可绘制对象中。
【讨论】:
【参考方案7】:你不能用官方的方法来模仿棒棒糖之前的海拔。
您可以使用一些可绘制对象在组件中制作阴影。例如,Google 在 CardView 中使用了这种方式。
ViewCompat.setElevation(View, int)
目前仅在 API21+ 上创建阴影。如果你检查后面的代码,这个方法会调用:
API 21+:
@Override
public void setElevation(View view, float elevation)
ViewCompatLollipop.setElevation(view, elevation);
API
@Override
public void setElevation(View view, float elevation)
【讨论】:
以上是关于如何为 Pre-lollipop 实现 Material-design Elevation的主要内容,如果未能解决你的问题,请参考以下文章
Lollipop RippleDrawable 与 Pre-Lollipop 的选择器
Media Session Compat 未在 Pre-Lollipop 上显示锁屏控件
用于 Pre-Lollipop 设备的具有弯曲背景的 Android Ripple 按钮
我的 Material Design ActionBar 或 Lollipop 设备中的 StatusBar 上没有显示颜色,但在 Pre-Lollipop 设备的 ActionBar 中显示。为啥?