制造渐变的倒影效果
英文原文: http://andraskindler.com/blog/2014/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;
}
}