前段时间写了关于Android圆角ImageView的博客,感觉写的太差了,其实不用那么麻烦,直接重写一下onMeasure方法就行了。
需求:两张图片平分屏幕,高度可以不固定,保证图片能完全铺满ImageView且不能变形。也就是这样的效果:
如果不自定义ImageView很难做到,所以这里覆写ImageView来实现,下面是代码,里面有注释(为了怕中文乱码,我蛋疼的加了英文注释)。由于采用了LruCache技术图片的点击效果莫名其妙的被屏蔽掉了,求各位大牛解答。
package cn.smc.roundimageview; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.graphics.Xfermode; import android.graphics.Bitmap.Config; import android.graphics.PorterDuff.Mode; import android.graphics.drawable.Drawable; import android.support.v4.util.LruCache; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; /** * @author shang ming chao * * */ public class RoundImageView extends ImageView { private Paint mPaint; private Xfermode mXfermode = new PorterDuffXfermode(Mode.DST_IN); private Bitmap mClipBitmap; private int mRoundBorderRadius;//corners radius private static final int DEFAULT_ROUND_BORDER_RADIUS = 50;//default corners radius private LruCache<String, Bitmap> mLruCache; private int realWidth;//drawn width private int realHeight;//drawn height private Drawable drawable;//source image drawable public OnTouchListener onTouchListener; public RoundImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub init(); } public RoundImageView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub init(); } public RoundImageView(Context context) { super(context); // TODO Auto-generated constructor stub init(); } public void init() { drawable = getDrawable();//source image drawable mRoundBorderRadius = DEFAULT_ROUND_BORDER_RADIUS; mPaint = new Paint(); mPaint.setAntiAlias(true); int maxMemory = (int) Runtime.getRuntime().maxMemory();//32M int cacheMemory = maxMemory / 8;//4M mLruCache = new LruCache<String, Bitmap>(cacheMemory) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } }; onTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: changeLight((ImageView) view, 0); //view.performClick(); break; case MotionEvent.ACTION_DOWN: changeLight((ImageView) view, -80); break; case MotionEvent.ACTION_MOVE: // changeLight(view, 0); break; case MotionEvent.ACTION_CANCEL: changeLight((ImageView) view, 0); break; default: break; } return false;//must return false,then onclick() will be invoked } }; this.setFocusable(true); this.setClickable(true); this.setLongClickable(true); this.setOnTouchListener(onTouchListener); } private void changeLight(ImageView imageview, int brightness) { ColorMatrix matrix = new ColorMatrix(); matrix.set(new float[] { 1, 0, 0, 0, brightness, 0, 1, 0, 0, brightness, 0, 0, 1, 0, brightness, 0, 0, 0, 1, 0 }); imageview.setColorFilter(new ColorMatrixColorFilter(matrix)); imageview.invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec);//measure firstly realWidth = getMeasuredWidth();//get the real-width(screenWidth/2) float srcWidth = drawable.getIntrinsicWidth();//get the width of image float srcHeight = drawable.getIntrinsicHeight();//get the height of image int expectHeight = Math.round(realWidth*srcHeight/srcWidth);//calculate the ratio realHeight = expectHeight; int myHeightMeasureSpec = MeasureSpec.makeMeasureSpec(expectHeight,MeasureSpec.EXACTLY); setMeasuredDimension(widthMeasureSpec, myHeightMeasureSpec);//specify our height } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub //super.onDraw(canvas); Bitmap bmp = getBitmapFromLruCache("url"); if (bmp == null) { if (drawable != null) { bmp = getClippedBitmap(drawable);//get round image canvas.drawBitmap(bmp, 0, 0, mPaint); addBitmapToLruCache("url",bmp); } } else { mPaint.setXfermode(null); canvas.drawBitmap(bmp, 0.0f, 0.0f, mPaint); return; } } //get rounded bitmap,the drawable has been clipped private Bitmap getClippedBitmap(Drawable drawable) { Bitmap bmp= Bitmap.createBitmap(realWidth, realHeight, Config.ARGB_8888); Canvas drawCanvas = new Canvas(bmp); drawable.setBounds(0, 0, realWidth, realHeight); drawable.draw(drawCanvas);//draw source image firstly if (mClipBitmap == null || mClipBitmap.isRecycled()) { mClipBitmap = getClipBitmap(); } mPaint.reset(); mPaint.setFilterBitmap(false); mPaint.setXfermode(mXfermode); drawCanvas.drawBitmap(mClipBitmap, 0, 0, mPaint);//then draw roundRect with dst_in mPaint.setXfermode(null); return bmp; } private Bitmap getClipBitmap() { Bitmap bitmap = Bitmap.createBitmap(realWidth, realHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.BLACK); canvas.drawRoundRect( new RectF(0, 0, realWidth, realHeight), mRoundBorderRadius, mRoundBorderRadius, paint); return bitmap; } public void addBitmapToLruCache(String key, Bitmap bitmap) { if (getBitmapFromLruCache(key) == null && bitmap != null) { mLruCache.put(key, bitmap); } } public Bitmap getBitmapFromLruCache(String key) { return mLruCache.get(key); } public int getRoundBorderRadius() { return mRoundBorderRadius; } public void setRoundBorderRadius(int mRoundBorderRadius) { if (this.mRoundBorderRadius != mRoundBorderRadius) { this.mRoundBorderRadius = mRoundBorderRadius; invalidate(); } } }
源码下载
下面的是以前丑陋的写法,虽然丑陋但有些地方还是有参考价值的。
Android自带的ImageView给人感觉很难看,我们希望图片是个圆角的、可以点击、有点击效果的,最重要的是:图片显示时不能因为缩放而变形。
ImageView有两个重要属性:一个是src(ImageView的前景图片),一个是Background(ImageView的背景)。在布局时,我们一般会指定ImageView的宽高,但这个宽和高不一定是图片的宽和高,所以为了让图片在这个ImageView上显示,Android Framework提供了8种图片缩放方式。最常用的就是center,centerCrop和centerInside。
但这个ImageView宽高比不一定是图片的宽高比。所以如果我们的图片不是透明背景的话,就会出现下面这样的情况,没有办法在保证不变形的情况下显示漂亮的图片:
蓝色部分是ImageView的背景。但我们想要达到这样的效果:
原图 center centerCrop centerInside
怎么办呢?只能通过真实的显示宽度来计算要显示的高度,也就是不能把高度定死。(比如屏幕中两张图片平分屏幕,权值一样,那宽度多少像素我们不知道,所以高度多少我们也没办法指定),所以要自定义(覆写)ImageView:
①在自定义ImageView的onDraw方法里自己画出一个圆角图片:
<span style="color:#c0c0c0;">//get rounded bitmap,the drawable has been clipped private Bitmap getClippedBitmap(Drawable drawable) { Bitmap bmp= Bitmap.createBitmap(realWidth, realHeight, Config.ARGB_8888); Canvas drawCanvas = new Canvas(bmp); drawable.setBounds(0, 0, realWidth, realHeight); drawable.draw(drawCanvas);//draw source image firstly if (mClipBitmap == null || mClipBitmap.isRecycled()) { mClipBitmap = getClipBitmap(); } mPaint.reset(); mPaint.setFilterBitmap(false); mPaint.setXfermode(mXfermode); drawCanvas.drawBitmap(mClipBitmap, 0, 0, mPaint);//then draw roundRect with dst_in mPaint.setXfermode(null); return bmp; } private Bitmap getClipBitmap() { Bitmap bitmap = Bitmap.createBitmap(realWidth, realHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.BLACK); canvas.drawRoundRect( new RectF(0, 0, realWidth, realHeight), mRoundBorderRadius, mRoundBorderRadius, paint); return bitmap; }</span>
在网上看到有人(不知哪位大牛)用WeakReference弱引用来防止绘图开销。这里借鉴了一下:<span style="color:#c0c0c0;">protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub // super.onDraw(canvas); Bitmap bmp = (mBufferBitmap == null ? null : mBufferBitmap.get()); if (bmp == null || bmp.isRecycled()) { if (drawable != null) { bmp = getClippedBitmap(drawable);//get round image canvas.drawBitmap(bmp, 0, 0, null); mBufferBitmap = new WeakReference<Bitmap>(bmp);//save } } else { mPaint.setXfermode(null); canvas.drawBitmap(bmp, 0.0f, 0.0f, mPaint); return; } }</span>
如果需要图片有点击变暗效果,可以利用矩阵来实现:<span style="color:#c0c0c0;">onTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: changeLight((ImageView) view, 0); //view.performClick(); break; case MotionEvent.ACTION_DOWN: changeLight((ImageView) view, -80); break; case MotionEvent.ACTION_MOVE: // changeLight(view, 0); break; case MotionEvent.ACTION_CANCEL: changeLight((ImageView) view, 0); break; default: break; } return false;//must return false,then onclick() will be invoked } }; this.setFocusable(true); this.setClickable(true); this.setLongClickable(true); this.setOnTouchListener(onTouchListener);</span><span style="color:#c0c0c0;">private void changeLight(ImageView imageview, int brightness) { ColorMatrix matrix = new ColorMatrix(); matrix.set(new float[] { 1, 0, 0, 0, brightness, 0, 1, 0, 0, brightness, 0, 0, 1, 0, brightness, 0, 0, 0, 1, 0 }); imageview.setColorFilter(new ColorMatrixColorFilter(matrix)); }</span>
②在Activity类中调整最终显示的高度(为了保证图片的宽高比)。<span style="color:#c0c0c0;">RoundImageView iv_am = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); iv_am = (RoundImageView) this.findViewById(R.id.iv_am); setImageViewHeightAdjust(iv_am); } public void setImageViewHeightAdjust(final RoundImageView mImageView) { mImageView.setRoundBorderRadius(55);//corners radius ViewTreeObserver vto2 = mImageView.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mImageView.getViewTreeObserver().removeGlobalOnLayoutListener(this); int realWidth = mImageView.getWidth(); //int realHeight = mImageView.getHeight(); int srcWidth = mImageView.getDrawable().getIntrinsicWidth(); int srcHeight = mImageView.getDrawable().getIntrinsicHeight(); LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); param.width = realWidth; param.height = realWidth*srcHeight/srcWidth;//calculate real_height by the width/height param.setMargins(5, 5, 5, 5); mImageView.setLayoutParams(param); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); }</span>③在布局文件中使用时,用自己定义的ImageView。
<span style="color:#c0c0c0;"><com.smc.roundimageview.RoundImageView android:id="@+id/iv_am" android:layout_width="200dp" android:layout_height="100dp" android:clickable="true" android:src="@drawable/image_tuisong" /></span>
好了,现在这个ImageView就可以按照我们想要的样子显示图片了。有些人就要问了:有必要这么写吗?有必要吗?
当你有两个或多个ImageView平分屏幕时,每个ImageView分多少像素你肯定不知道,那你就没办法在布局文件中去指定高或者宽了,如果你指定了那么不同大小和分辨率的设备上肯定会变形。在xml文件中指定的高度是无效的,我们在程序中自己算view的高。程序中也许存在一个小bug:在两个ImageView平分屏幕时,如果在java代码中指定两个ImageView的margin,那么第二张图片的margin-right会失效,只能通过xml文件指定最后一个(第二张)ImageView的margin。太累了,写个程序容易吗,连lol的力气都没有了,人在塔在,德玛西亚!
源码下载