【问题标题】:How to implement the Material-design Elevation for Pre-lollipop如何为 Pre-lollipop 实现 Material-design Elevation
【发布时间】:2015-08-26 23:52:52
【问题描述】:

Google 已经展示了一些很好的方法,可以在 Lollipop here 上显示高程效果。

android:elevation="2dp"

对于按钮,

android:stateListAnimator="@anim/button_state_list_animator"

如何在没有 3rd 方库的情况下模拟棒棒糖前版本的提升效果?

【问题讨论】:

标签: android material-design android-appcompat android-elevation


【解决方案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://stackoverflow.com/a/25683148/3879847

    感谢用户@Repo..

    更新:如果你想改变这个drawable的颜色,试试下面的@Irfan回答↓

    https://stackoverflow.com/a/40815944/3879847

    【讨论】:

    • 如果我想使用蓝色背景的按钮怎么办
    • 如何使用此方法自定义海拔值
    【解决方案3】:

    要为棒棒糖之前的设备带来动态的动画阴影,您必须:

    1. 将视图的黑色形状绘制到位图
    2. 使用高程作为半径模糊该形状。您可以使用 RenderScript 来做到这一点。这与 Lollipop 使用的方法不完全一样,但效果很好,而且很容易添加到现有视图中。
    3. 在视图下方绘制那个模糊的形状。可能最好的地方是drawChild 方法。 您还必须覆盖 setElevationsetTranslationZ,覆盖布局中的子视图绘制,关闭 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_width="wrap_content"
          android:layout_height="wrap_content"
          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_width="wrap_content"
              android:layout_height="wrap_content"
              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) 图像 herehere,您可以从中选择、自定义和复制到项目的可绘制对象中。

        【讨论】:

          【解决方案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) {
          
          }
          

          【讨论】:

            猜你喜欢
            • 2017-05-19
            • 1970-01-01
            • 2016-03-13
            • 2016-01-23
            • 1970-01-01
            • 1970-01-01
            • 2015-09-12
            • 2016-10-26
            • 2017-06-29
            相关资源
            最近更新 更多