制造渐变的倒影效果

英文原文: http://andraskindler.com/blog/2014/creating-view-reflections/

转载注明出处

Creating View reflections

虽然拟物化的设计的时代已经过去,但你不应该忘记在那个时代学到的所有东西。其中一种效果就是倒影,一种可好可坏的东西-过度使用可能会让你的UI看起来臃肿,但有时候它又是改善用户体验的良药。这篇文章将演示如何为任意的View制造一个渐变的倒影效果。

整个过程可以分为三大步:

1.建立一个你想要将之倒影的界面(就是一个view),但是不要把它添加到根view中。

2.布局与渲染这个View,以便获得这个View的bitmap。

3.为这个bitmap添加倒影,并把它绘制在屏幕上。

构建UI

第一步非常简单。和平常一样只是记住不要把根view添加到你的布局层级中。示例中使用的是一个红底白字的TextView,但是这种效果并不是局限于一个单独的View,还可以是更复杂的布局(ViewGroup)

final TextView textView = new TextView(this);
textView.setText("test");
textView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
textView.setTextColor(Color.WHITE);
textView.setTextSize(40);
textView.setBackgroundColor(Color.RED);
textView.setPadding(100, 20, 100, 20);

布局与渲染 

布局过程包括两步:测量view,布局view。测量是通过[measure()](http://developer.android.com/reference/android/view/View.html#measure(int, int)) 方法完成,该方法负责决定view与其子view的尺寸需求。调用measure() 之后,getMeasuredWidth() 和getMeasuredHeight() 方法便能返回正确的值,这个值将在layout()方法中使用。layout()方法可以强制所有的view根据尺寸参数放置子view的位置。

final int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
textView.measure(measureSpec, measureSpec);
textView.layout(0, 0, textView.getMeasuredWidth(), textView.getMeasuredHeight());

下一步,使用draw()方法捕获view的bitmap ,该方法将它渲染到画布上。

final Bitmap b = Bitmap.createBitmap(textView.getWidth(), textView.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas c = new Canvas(b);
textView.draw(c);

制造倒影

产生倒影可以分为四个步骤,下图展示了每一步可以得到的效果:

你需要决定倒影的大小(用原view的百分比来衡量也许不错),中间间隙的高度,这两个都应该以像素(px)为单位。你还需要一个新的bitmap(和原bitmap大小相同,包括了倒影和间隙),以及它的Canvas。

final int reflectionHeight = original.getHeight() * percentage;
Bitmap bitmapWithReflection = Bitmap.createBitmap(original.getWidth(), (original.getHeight() + reflectionHeight + gap), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmapWithReflection);

首先,绘制原始的bitmap:

canvas.drawBitmap(original, 0, 0, null);

然后添加一个间隙,这个间隙在这里用透明的画笔来代替:

final Paint transparentPaint = new Paint();
transparentPaint.setARGB(0, 255, 255, 255);
canvas.drawRect(0, original.getHeight(), original.getWidth(), original.getHeight() + gap, transparentPaint);

下一步:倒影,我们将使用一个修改了的matrix 来让图像沿x轴翻转,然后将之绘制在画布中:

final Matrix matrix = new Matrix();
matrix.preScale(1, -1);
canvas.drawBitmap(Bitmap.createBitmap(original, 0, original.getHeight() - reflectionHeight, 
                                        original.getWidth(), reflectionHeight, matrix, false), 0, original.getHeight() + gap, null);

最后一点很重要,使用合适的 LinearGradient为倒影添加一个渐变效果:

final Paint fadePaint = new Paint();
fadePaint.setShader(new LinearGradient(0, original.getHeight(), 0, original.getHeight() + reflectionHeight + gap, 0x70ffffff, 0x00ffffff, Shader.TileMode.CLAMP));
fadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawRect(0, original.getHeight(), original.getWidth(), bitmapWithReflection.getHeight() + gap, fadePaint);

好了,现在你就能得到一个显示一个view的渐变倒影效果的bitmap了,你可以将这个bitmap显示在ImageView 中,或者作为view的背景,或者在View子类的onDraw() 方法中使用。

被忘了调用original 的recycle() 避免内存管理的问题。

完整的代码在这里at this Gist

可能打不开,我直接复制下来吧:

package com.inch.android.sandbox.activity;
 
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
 
public class ReflectionActivity extends BaseActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        final FrameLayout root = new FrameLayout(this);
        setContentView(root);
 
        final TextView textView = new TextView(this);
        textView.setText("test");
        textView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
        textView.setTextColor(Color.WHITE);
        textView.setTextSize(40);
        textView.setGravity(Gravity.CENTER_HORIZONTAL);
        textView.setBackgroundColor(Color.RED);
        textView.setPadding(100, 20, 100, 20);
 
        final int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        textView.measure(measureSpec, measureSpec);
        textView.layout(0, 0, textView.getMeasuredWidth(), textView.getMeasuredHeight());
        final Bitmap b = Bitmap.createBitmap(textView.getWidth(), textView.getHeight(), Bitmap.Config.ARGB_8888);
        final Canvas c = new Canvas(b);
        textView.draw(c);
 
        final ImageView imageView = new ImageView(this);
        imageView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
        imageView.setImageBitmap(createReflection(b, 1, 10));
        root.addView(imageView);
    }
 
    private Bitmap createReflection(Bitmap original, float percentage, int gap) {
 
        final int reflectionHeight = (int) (original.getHeight() * percentage);
        Bitmap bitmapWithReflection = Bitmap.createBitmap(original.getWidth(), (original.getHeight() + reflectionHeight + gap), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmapWithReflection);
 
        // original image
        canvas.drawBitmap(original, 0, 0, null);
        // gap drawing
        final Paint transparentPaint = new Paint();
        transparentPaint.setARGB(0, 255, 255, 255);
        canvas.drawRect(0, original.getHeight(), original.getWidth(), original.getHeight() + gap, transparentPaint);
        // reflection
        final Matrix matrix = new Matrix();
        matrix.preScale(1, -1);
        canvas.drawBitmap(Bitmap.createBitmap(original, 0, original.getHeight() - reflectionHeight, original.getWidth(), reflectionHeight, matrix, false), 0, original.getHeight() + gap, null);
        // reflection shading
        final Paint fadePaint = new Paint();
        fadePaint.setShader(new LinearGradient(0, original.getHeight(), 0, original.getHeight() + reflectionHeight + gap, 0x70ffffff, 0x00ffffff, Shader.TileMode.CLAMP));
        fadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        canvas.drawRect(0, original.getHeight(), original.getWidth(), bitmapWithReflection.getHeight() + gap, fadePaint);
 
        original.recycle();
        return bitmapWithReflection;
    }
 
}