如何保持多层的ImageViews,并保持最大的纵横比?
假设我有多个图片,需要将其中一个放在另一个的顶部,有些可能会出现某种animation,有些甚至可能是可拖动的。
通常占据整个屏幕的最大的一个将在Z坐标的底部(我们称之为backgroundImageView),而其余的则全部出现在顶部(而在其他顶部)。
像这样:
- backgroundImageView
- imageView1居中。
- imageView2,60%,左上angular的60%
- …
如果我使用FrameLayout(这似乎是最好的解决scheme),那么backgroundImageView的尺寸会非常合适,但是我怎样才能强制其他图层相应地调整自己?
我想我需要以某种方式得到其他图层的位置以及如何设置其大小。
简单的方法是确保所有图层具有完全相同的大小,但是在animation或拖动视图时可能会占用大量内存并变得非常慢。 如果某些图层的内容很小,这将是一个巨大的浪费。
这是一个显示带有附加图层的图像的类:
import java.util.ArrayList; import java.util.Iterator; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; import android.widget.ImageView; public class LayeredImageView extends ImageView { private final static String TAG = "LayeredImageView"; private ArrayList<Layer> mLayers; private Matrix mDrawMatrix; private Resources mResources; public LayeredImageView(Context context) { super(context); init(); } public LayeredImageView(Context context, AttributeSet set) { super(context, set); init(); int[] attrs = { android.R.attr.src }; TypedArray a = context.obtainStyledAttributes(set, attrs); TypedValue outValue = new TypedValue(); if (a.getValue(0, outValue)) { setImageResource(outValue.resourceId); } a.recycle(); } private void init() { mLayers = new ArrayList<Layer>(); mDrawMatrix = new Matrix(); mResources = new LayeredImageViewResources(); } @Override protected boolean verifyDrawable(Drawable dr) { for (int i = 0; i < mLayers.size(); i++) { Layer layer = mLayers.get(i); if (layer.drawable == dr) { return true; } } return super.verifyDrawable(dr); } @Override public void invalidateDrawable(Drawable dr) { if (verifyDrawable(dr)) { invalidate(); } else { super.invalidateDrawable(dr); } } @Override public Resources getResources() { return mResources; } @Override public void setImageBitmap(Bitmap bm) throws RuntimeException { String detailMessage = "setImageBitmap not supported, use: setImageDrawable() " + "or setImageResource()"; throw new RuntimeException(detailMessage); } @Override public void setImageURI(Uri uri) throws RuntimeException { String detailMessage = "setImageURI not supported, use: setImageDrawable() " + "or setImageResource()"; throw new RuntimeException(detailMessage); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Matrix matrix = getImageMatrix(); if (matrix != null) { int numLayers = mLayers.size(); boolean pendingAnimations = false; for (int i = 0; i < numLayers; i++) { mDrawMatrix.set(matrix); Layer layer = mLayers.get(i); if (layer.matrix != null) { mDrawMatrix.preConcat(layer.matrix); } if (layer.animation == null) { draw(canvas, layer.drawable, mDrawMatrix, 255); } else { Animation a = layer.animation; if (!a.isInitialized()) { Rect bounds = layer.drawable.getBounds(); Drawable parentDrawable = getDrawable(); if (parentDrawable != null) { Rect parentBounds = parentDrawable.getBounds(); a.initialize(bounds.width(), bounds.height(), parentBounds.width(), parentBounds.height()); } else { a.initialize(bounds.width(), bounds.height(), 0, 0); } } long currentTime = AnimationUtils.currentAnimationTimeMillis(); boolean running = a.getTransformation(currentTime, layer.transformation); if (running) { // animation is running: draw animation frame Matrix animationFrameMatrix = layer.transformation.getMatrix(); mDrawMatrix.preConcat(animationFrameMatrix); int alpha = (int) (255 * layer.transformation.getAlpha()); // Log.d(TAG, "onDraw ********** [" + i + "], alpha: " + alpha + ", matrix: " + animationFrameMatrix); draw(canvas, layer.drawable, mDrawMatrix, alpha); pendingAnimations = true; } else { // animation ended: set it to null layer.animation = null; draw(canvas, layer.drawable, mDrawMatrix, 255); } } } if (pendingAnimations) { // invalidate if any pending animations invalidate(); } } } private void draw(Canvas canvas, Drawable drawable, Matrix matrix, int alpha) { canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.concat(matrix); drawable.setAlpha(alpha); drawable.draw(canvas); canvas.restore(); } public Layer addLayer(Drawable d, Matrix m) { Layer layer = new Layer(d, m); mLayers.add(layer); invalidate(); return layer; } public Layer addLayer(Drawable d) { return addLayer(d, null); } public Layer addLayer(int idx, Drawable d, Matrix m) { Layer layer = new Layer(d, m); mLayers.add(idx, layer); invalidate(); return layer; } public Layer addLayer(int idx, Drawable d) { return addLayer(idx, d, null); } public void removeLayer(Layer layer) { layer.valid = false; mLayers.remove(layer); } public void removeAllLayers() { Iterator<Layer> iter = mLayers.iterator(); while (iter.hasNext()) { LayeredImageView.Layer layer = iter.next(); layer.valid = false; iter.remove(); } invalidate(); } public int getLayersSize() { return mLayers.size(); } public class Layer { private Drawable drawable; private Animation animation; private Transformation transformation; private Matrix matrix; private boolean valid; private Layer(Drawable d, Matrix m) { drawable = d; transformation = new Transformation(); matrix = m; valid = true; Rect bounds = d.getBounds(); if (bounds.isEmpty()) { if (d instanceof BitmapDrawable) { int right = d.getIntrinsicWidth(); int bottom = d.getIntrinsicHeight(); d.setBounds(0, 0, right, bottom); } else { String detailMessage = "drawable bounds are empty, use d.setBounds()"; throw new RuntimeException(detailMessage); } } d.setCallback(LayeredImageView.this); } public void startLayerAnimation(Animation a) throws RuntimeException { if (!valid) { String detailMessage = "this layer has already been removed"; throw new RuntimeException(detailMessage); } transformation.clear(); animation = a; if (a != null) { a.start(); } invalidate(); } public void stopLayerAnimation(int idx) throws RuntimeException { if (!valid) { String detailMessage = "this layer has already been removed"; throw new RuntimeException(detailMessage); } if (animation != null) { animation = null; invalidate(); } } } private class LayeredImageViewResources extends Resources { public LayeredImageViewResources() { super(getContext().getAssets(), new DisplayMetrics(), null); } @Override public Drawable getDrawable(int id) throws NotFoundException { Drawable d = super.getDrawable(id); if (d instanceof BitmapDrawable) { BitmapDrawable bd = (BitmapDrawable) d; bd.getBitmap().setDensity(DisplayMetrics.DENSITY_DEFAULT); bd.setTargetDensity(DisplayMetrics.DENSITY_DEFAULT); } return d; } } }
以及如何使用它:
final LayeredImageView v = new LayeredImageView(this); Resources res = v.getResources(); v.setImageResource(R.drawable.background); Matrix m; m = new Matrix(); m.preTranslate(81, 146); // pixels to offset final Layer layer1 = v.addLayer(res.getDrawable(R.drawable.layer1), m); m = new Matrix(); m.preTranslate(62, 63); // pixels to offset final Layer layer0 = v.addLayer(0, res.getDrawable(R.drawable.layer0), m); final AnimationDrawable ad = new AnimationDrawable(); ad.setOneShot(false); Drawable frame1, frame2; frame1 = res.getDrawable(R.drawable.layer0); frame2 = res.getDrawable(R.drawable.layer1); ad.addFrame(frame1, 3000); ad.addFrame(frame2, 1000); ad.addFrame(frame1, 250); ad.addFrame(frame2, 250); ad.addFrame(frame1, 250); ad.addFrame(frame2, 250); ad.addFrame(frame1, 250); ad.addFrame(frame2, 250); ad.setBounds(200, 20, 300, 120); v.addLayer(1, ad); v.post(new Runnable() { @Override public void run() { ad.start(); } }); int[] colors = { 0xeeffffff, 0xee0038a8, 0xeece1126, }; GradientDrawable gd = new GradientDrawable(Orientation.TOP_BOTTOM, colors); gd.setBounds(0, 0, 100, 129); gd.setCornerRadius(20); gd.setStroke(5, 0xaa666666); final Matrix mm = new Matrix(); mm.preTranslate(200, 69); // pixels to offset mm.preRotate(20, 50, 64.5f); final Layer layer2 = v.addLayer(2, gd, mm); final Animation as = AnimationUtils.loadAnimation(this, R.anim.anim_set); final Runnable action1 = new Runnable() { @Override public void run() { Animation a; Interpolator i; i = new Interpolator() { @Override public float getInterpolation(float input) { return (float) Math.sin(input * Math.PI); } }; as.setInterpolator(i); layer0.startLayerAnimation(as); a = new TranslateAnimation(0, 0, 0, 100); a.setDuration(3000); i = new Interpolator() { @Override public float getInterpolation(float input) { float output = (float) Math.sin(Math.pow(input, 2.5f) * 12 * Math.PI); return (1-input) * output; } }; a.setInterpolator(i); layer1.startLayerAnimation(a); a = new AlphaAnimation(0, 1); i = new Interpolator() { @Override public float getInterpolation(float input) { return (float) (1 - Math.sin(input * Math.PI)); } }; a.setInterpolator(i); a.setDuration(2000); layer2.startLayerAnimation(a); } }; OnClickListener l1 = new OnClickListener() { @Override public void onClick(View view) { action1.run(); } }; v.setOnClickListener(l1); v.postDelayed(action1, 2000); // final float[] values = new float[9]; // final float[] pts = new float[2]; // final Matrix inverse = new Matrix();; // OnTouchListener l = new OnTouchListener() { // @Override // public boolean onTouch(View view, MotionEvent event) { // int action = event.getAction(); // if (action != MotionEvent.ACTION_UP) { // if (inverse.isIdentity()) { // v.getImageMatrix().invert(inverse); // Log.d(TAG, "onTouch set inverse"); // } // pts[0] = event.getX(); // pts[1] = event.getY(); // inverse.mapPoints(pts); // // mm.getValues(values); // // gd's bounds are (0, 0, 100, 129); // values[Matrix.MTRANS_X] = pts[0] - 100 / 2; // values[Matrix.MTRANS_Y] = pts[1] - 129 / 2; // mm.setValues(values); // v.invalidate(); // } // return false; // } // }; // v.setOnTouchListener(l); setContentView(v);
anim_set.xml如下所示:
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="true" > <rotate android:fromDegrees="0" android:toDegrees="30" android:pivotX="50%" android:pivotY="50%" android:duration="2500" /> <scale android:fromXScale="1" android:toXScale="1.8" android:fromYScale="1" android:toYScale="1.8" android:pivotX="50%" android:pivotY="50%" android:duration="2500" /> </set>
与以下图像:
background.png:
layer0.png:
layer1.png:
结果是:
重要的是 ,当从不同的drawable- *文件夹加载时,为了防止自动操作系统扩展的资源,必须使用从LayeredImageView.getResources()方法获得的Resources对象
玩的开心!
只是扩展ImageView并重写其onDraw方法,以绘制额外的图层
这是一个最小的varsion(animation增强版本在第二个答案):
import java.util.ArrayList; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.util.AttributeSet; import android.util.Log; import android.widget.ImageView; public class LayeredImageView extends ImageView { private final static String TAG = "LayeredImageView"; ArrayList<Bitmap> mLayers; public LayeredImageView(Context context) { super(context); init(); } public LayeredImageView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mLayers = new ArrayList<Bitmap>(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Matrix matrix = getImageMatrix(); if (matrix != null) { int numLayers = mLayers.size(); for (int i = 0; i < numLayers; i++) { Bitmap b = mLayers.get(i); canvas.drawBitmap(b, matrix, null); } } } public void addLayer(Bitmap b) { mLayers.add(b); invalidate(); } public void addLayer(int idx, Bitmap b) { mLayers.add(idx, b); invalidate(); } public void removeLayer(int idx) { mLayers.remove(idx); } public void removeAllLayers() { mLayers.clear(); invalidate(); } public int getLayersSize() { return mLayers.size(); } }
以及如何在您的活动中使用它:
LayeredImageView v = new LayeredImageView(this); v.setImageResource(R.drawable.background); Resources res = getResources(); v.addLayer(BitmapFactory.decodeResource(res, R.drawable.layer0)); v.addLayer(0, BitmapFactory.decodeResource(res, R.drawable.layer1)); setContentView(v);
在这里你有3个图像:
background.png
layer0.png
layer1.png
和三个以上的组合
最后从模拟器中捕获屏幕