How to implement the Material-design Elevation for Pre-lollipop

前端 未结 7 1355
南旧
南旧 2020-11-27 11:44

Google has shown some nice ways that elevation effect are shown on Lollipop here.

android:elevation=\"2dp\"

for buttons,

an         


        
相关标签:
7条回答
  • 2020-11-27 12:24

    You can mimic the elevation on pre-Lollipop with a official method.

    I achieve same effect using,

      android:background="@android:drawable/dialog_holo_light_frame"
    

    My tested output:

    enter image description here

    reference - https://stackoverflow.com/a/25683148/3879847

    Thanks to user @Repo..

    Update : If you want change color of this drawable try @Irfan answer below ↓

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

    0 讨论(0)
  • 2020-11-27 12:25

    u can easily simulate it by declaring a drawable like this -

    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>
    

    and use it int ur main xml like -

     android:background="@drawable/shadow"
    
    0 讨论(0)
  • 2020-11-27 12:27

    You can either hack it using a card-view:

    <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>
    

    Or look at using this third party library: https://github.com/rey5137/Material (see wiki article on button https://github.com/rey5137/Material/wiki/Button)

    0 讨论(0)
  • 2020-11-27 12:28

    To bring dynamic, animated shadows to pre-Lollipop devices you have to:

    1. Draw a black shape of a view to a bitmap
    2. Blur that shape using elevation as a radius. You can do that using RenderScript. It's not exactly the method Lollipop's using, but gives good results and is easy to add to existing views.
    3. Draw that blurred shape beneath the view. Probably the best place is the drawChild method. You also have to override setElevation and setTranslationZ, override child view drawing in layouts, turn off clip-to-padding and implement state animators.

    enter image description here

    That's a lot of work, but it gives the best looking, dynamic shadows with response animations. I'm not sure why you'd like to achieve that without third party libraries. If you wish, you can analyze sources of Carbon and port the parts you'd like to have in your app:

    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();
    }
    
    0 讨论(0)
  • 2020-11-27 12:40

    Create a 9-patch image with stretchable patches defined on an image with shadow around it.

    enter image description here

    Add this 9-patch image as a background of your button with a padding so that the shadow is visible.

    You can find some pre-defined 9-patch (.9.png) images here or here from where you can select, customize and copy to your project's drawable.

    0 讨论(0)
  • 2020-11-27 12:42

    You can't mimic the elevation on pre-Lollipop with a official method.

    You can use some drawables to make the shadow in your component. Google uses this way in CardView for example.

    The ViewCompat.setElevation(View, int) currently creates the shadow only on API21+. If you check the code behind, this method calls:

    API 21+:

      @Override
        public void setElevation(View view, float elevation) {
            ViewCompatLollipop.setElevation(view, elevation);
        }
    

    API < 21

    @Override
    public void setElevation(View view, float elevation) {
    
    }
    
    0 讨论(0)
提交回复
热议问题