[Android] PorterDuff使用实例----实现新浪微博图片下载效果

先上效果图,如demo_sinaweibo.gif

[Android] PorterDuff使用实例----实现新浪微博图片下载效果_第1张图片

由效果图,下半部分是简单的效果叠加,上半部分是新浪微博加载图片显示进度的效果,显示进度的半透明区域只与根据背景图的非透明区域叠加,背景图的透明区域仍为透明。
为实现此要求,联想到APIDemos中的com.example.android.apis.graphics.Xfermodes,可以自定义组件在组件的绘制过程中设置PorterDuff.Mode即可实现。
另效果图中显示当下载进度超过50%时,重新设置了背景图。

本次自定义组件选择继承ImageView来实现,名为PorterDuffView。将ImageView新增一porterduffMode。在该模式下,将可显示图片加载进度;否则按ImageView原有规则显示图片。


1.PorterDuffView的XML编码

设置PorterDuffView的porterduffMode,可有两种方式,一为在xml中设置,一为在代码设置。

xml中设置的实现:
在/res/values下新建一"attrs.xml",内容如下:

	<?xml version="1.0" encoding="UTF-8"?>
	<resources>
		<!--请参阅
		[android_sdk]\platforms\android-n\data\res\values\attrs.xml
		-->
		<declare-styleable name="porterduff.PorterDuffView">
			<attr name="porterduffMode" format="boolean"></attr>
		</declare-styleable>
	</resources>

在此声明中,属性名为"porterduffMode",对其赋值范围为boolean型。赋值范围的规范可参考:[android_sdk]\platforms\android-n\data\res\values\attrs.xml

有声明后,在layout下的布局文件,需首先在原有基础上添加一命名空间,代码如下:

	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:porterduff="http://schemas.android.com/apk/res/lab.sodino.porterduff"
    android:orientation="vertical"
	.... ....
	></LinearLayout>

命名空间名为"porterduff",赋值规则为"http://schemas.android.com/apk/res/"+App包名。
在布局文件中使用PorterDuffView时,几乎与ImageView一致,设置porterduffMode如下:

	<lab.sodino.porterduff.PorterDuffView
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:id="@+id/porterDuffView"
	    android:src="@drawable/loading"
	    android:layout_gravity="center"
	    porterduff:porterduffMode="true"
	></lab.sodino.porterduff.PorterDuffView>

另,在代码中设置porterduffMode为直接调用setPorterDuffMode(boolean),参数为true即可。


2.PorterDuffView的Java编码
需要重写ImageView的onDraw()方法。
当其porterduffMode值为true时,显示图片加载进度。否则按ImageView 的规则显示图片。
由效果图可知,需要生成一半透明的前景图。
生成前景图的代码为:

	/** 生成一宽与背景图片等同高为1像素的Bitmap,。 */
	private static Bitmap createForegroundBitmap(int w) {
		Bitmap bm = Bitmap.createBitmap(w, FG_HEIGHT, Bitmap.Config.ARGB_8888);
		Canvas c = new Canvas(bm);
		Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
		p.setColor(FOREGROUND_COLOR);
		c.drawRect(0, 0, w, FG_HEIGHT, p);
		return bm;
	}

为节约内存消耗,生成的前景图Bitmap其高度只有1像素。那么在onDraw()方法中,需要根据加载进度,循环滴绘制叠加区域。代码如下:

			int tH = height - (int) (progress * height);
			for (int i = 0; i < tH; i++) {
				canvas.drawBitmap(bitmapFg, tmpW, tmpH + i, paint);
			}

在onDraw()方法中,调用Paint.setXfermode()绘制完叠加区域后,应再次对其设置值为null取消PorterDuff效果。

加载区域的进度值由PorterDuffView.setProgress()决定,因为每次设定新的进度后,应该调用 invalidate()及时刷新界面。
效果图中进度过50%时更改了背景图,方法为PorterDuffView.setBitmap(),该方法将重新计算Bitmap的宽高,并生成新的前景图,调用ImageView.setImageBitmap()请求对组件重新布局及刷新界面。

本文为 Sodino所有,转载请注明出处: http://blog.csdn.net/sodino/article/details/7741236
以下贴出Java代码,XML请看官自行实现:

ActPorterDuff.java

package lab.sodino.porterduff;

import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;

public class ActPorterDuff extends Activity implements OnSeekBarChangeListener {
	private SeekBar seekbar;
	private PorterDuffView porterDuffView;
	private int currentId;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		seekbar = (SeekBar) findViewById(R.id.seekbar);
		seekbar.setOnSeekBarChangeListener(this);
		float progress = seekbar.getProgress() * 1.0f / seekbar.getMax();
		porterDuffView = (PorterDuffView) findViewById(R.id.porterDuffView);
		porterDuffView.setProgress(progress);
	}

	public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
		porterDuffView.setProgress(progress * 1.0f / seekbar.getMax());

		if (progress > 50 && currentId != R.drawable.loading_2) {
			currentId = R.drawable.loading_2;
			porterDuffView.setBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.loading_2));
		} else if (progress <= 50 && currentId != R.drawable.loading) {
			currentId = R.drawable.loading;
			porterDuffView.setBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.loading));
		}
	}

	public void onStartTrackingTouch(SeekBar seekBar) {
	}

	public void onStopTrackingTouch(SeekBar seekBar) {
	}
}

PorterDuffView.java

package lab.sodino.porterduff;

import java.text.DecimalFormat;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;

/**
 * 自定义组件实现新浪微博的图片加载效果。<br/>
 * 
 * @author Sodino E-mail:[email protected]
 * @version Time:2012-7-9 上午01:55:04
 */
public class PorterDuffView extends ImageView {
	/** 前景Bitmap高度为1像素。采用循环多次填充进度区域。 */
	public static final int FG_HEIGHT = 1;
	/** 下载进度前景色 */
	// public static final int FOREGROUND_COLOR = 0x77123456;
	public static final int FOREGROUND_COLOR = 0x77ff0000;
	/** 下载进度条的颜色。 */
	public static final int TEXT_COLOR = 0xff7fff00;
	/** 进度百分比字体大小。 */
	public static final int FONT_SIZE = 30;
	private Bitmap bitmapBg, bitmapFg;
	private Paint paint;
	/** 标识当前进度。 */
	private float progress;
	/** 标识进度图片的宽度与高度。 */
	private int width, height;
	/** 格式化输出百分比。 */
	private DecimalFormat decFormat;
	/** 进度百分比文本的锚定Y中心坐标值。 */
	private float txtBaseY;
	/** 标识是否使用PorterDuff模式重组界面。 */
	private boolean porterduffMode;
	/** 标识是否正在下载图片。 */
	private boolean loading;

	public PorterDuffView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context, attrs);
	}

	/** 生成一宽与背景图片等同高为1像素的Bitmap,。 */
	private static Bitmap createForegroundBitmap(int w) {
		Bitmap bm = Bitmap.createBitmap(w, FG_HEIGHT, Bitmap.Config.ARGB_8888);
		Canvas c = new Canvas(bm);
		Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
		p.setColor(FOREGROUND_COLOR);
		c.drawRect(0, 0, w, FG_HEIGHT, p);
		return bm;
	}

	private void init(Context context, AttributeSet attrs) {
		if (attrs != null) {
			// //////////////////////////////////////////
			// int count = attrs.getAttributeCount();
			// for (int i = 0; i < count; i++) {
			// LogOut.out(this, "attrNameRes:" +
			// Integer.toHexString(attrs.getAttributeNameResource(i))//
			// + " attrName:" + attrs.getAttributeName(i)//
			// + " attrResValue:" + attrs.getAttributeResourceValue(i, -1)//
			// + " attrValue:" + attrs.getAttributeValue(i)//
			// );
			// }
			// //////////////////////////////////////////

			TypedArray typedArr = context.obtainStyledAttributes(attrs, R.styleable.porterduff_PorterDuffView);
			porterduffMode = typedArr.getBoolean(R.styleable.porterduff_PorterDuffView_porterduffMode, false);
		}
		Drawable drawable = getDrawable();
		if (porterduffMode && drawable != null && drawable instanceof BitmapDrawable) {
			bitmapBg = ((BitmapDrawable) drawable).getBitmap();
			width = bitmapBg.getWidth();
			height = bitmapBg.getHeight();
			// LogOut.out(this, "width=" + width + " height=" + height);
			bitmapFg = createForegroundBitmap(width);
		} else {
			// 不符合要求,自动设置为false。
			porterduffMode = false;
		}

		paint = new Paint();
		paint.setFilterBitmap(false);
		paint.setAntiAlias(true);
		paint.setTextSize(FONT_SIZE);

		// 关于FontMetrics的详情介绍,可见:
		// http://xxxxxfsadf.iteye.com/blog/480454
		Paint.FontMetrics fontMetrics = paint.getFontMetrics();
		// 注意观察本输出:
		// ascent:单个字符基线以上的推荐间距,为负数
		Log.d("ANDROID_LAB", "ascent:" + fontMetrics.ascent//
				// descent:单个字符基线以下的推荐间距,为正数
				+ " descent:" + fontMetrics.descent //
				// 单个字符基线以上的最大间距,为负数
				+ " top:" + fontMetrics.top //
				// 单个字符基线以下的最大间距,为正数
				+ " bottom:" + fontMetrics.bottom//
				// 文本行与行之间的推荐间距
				+ " leading:" + fontMetrics.leading);
		// 在此处直接计算出来,避免了在onDraw()处的重复计算
		txtBaseY = (height - fontMetrics.bottom - fontMetrics.top) / 2;

		decFormat = new DecimalFormat("0.0%");
	}

	public void onDraw(Canvas canvas) {
		if (porterduffMode) {
			int tmpW = (getWidth() - width) / 2, tmpH = (getHeight() - height) / 2;
			// 画出背景图
			canvas.drawBitmap(bitmapBg, tmpW, tmpH, paint);
			// 设置PorterDuff模式
			paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));
			// canvas.drawBitmap(bitmapFg, tmpW, tmpH - progress * height,
			// paint);
			int tH = height - (int) (progress * height);
			for (int i = 0; i < tH; i++) {
				canvas.drawBitmap(bitmapFg, tmpW, tmpH + i, paint);
			}

			// 立即取消xfermode
			paint.setXfermode(null);
			int oriColor = paint.getColor();
			paint.setColor(TEXT_COLOR);
			paint.setTextSize(FONT_SIZE);
			String tmp = decFormat.format(progress);
			float tmpWidth = paint.measureText(tmp);
			canvas.drawText(decFormat.format(progress), tmpW + (width - tmpWidth) / 2, tmpH + txtBaseY, paint);
			// 恢复为初始值时的颜色
			paint.setColor(oriColor);
		} else {
			Log.d("ANDROID_LAB", "onDraw super");
			super.onDraw(canvas);
		}
	}

	public void setProgress(float progress) {
		if (porterduffMode) {
			if (this.progress != progress) {
				this.progress = progress;
				// 刷新自身。
				invalidate();
			}
		}
	}

	public void setBitmap(Bitmap bg) {
		if (porterduffMode) {
			bitmapBg = bg;
			width = bitmapBg.getWidth();
			height = bitmapBg.getHeight();

			bitmapFg = createForegroundBitmap(width);

			Paint.FontMetrics fontMetrics = paint.getFontMetrics();
			txtBaseY = (height - fontMetrics.bottom - fontMetrics.top) / 2;

			setImageBitmap(bg);
			// 请求重新布局,将会再次调用onMeasure()
//			requestLayout();
		}
	}

	public boolean isLoading() {
		return loading;
	}

	public void setLoading(boolean loading) {
		this.loading = loading;
	}

	public void setPorterDuffMode(boolean bool) {
		porterduffMode = bool;
	}
}





你可能感兴趣的:(xml,android,新浪微博,layout,null,float)