可以播放GIF和显示圆形图片的自定义ImageView

本篇博客参考了郭神的 Android PowerImageView实现,可以播放动画的强大ImageView和鸿洋的Android BitmapShader 实战 实现圆形、圆角图片,做了一些修改和扩展,所以文章50%属于转载50%属于原创吧^^

预备知识

首先是用来控制GIF图片播放的类为android.graphics.Movie类,这个类很简单,常用的方法就几个:

static Movie decodeByteArray(byte[] data, int offset, int length)
static Movie decodeFile(String pathName)
static Movie decodeStream(InputStream is)
void draw(Canvas canvas, float x, float y, Paint paint)
void draw(Canvas canvas, float x, float y)
boolean setTime(int relativeMilliseconds)
int duration()
int height()
int width()
boolean isOpaque()


 

 静态方法decodeXXX就是从指定位置获得一个Movie对象。这些方法的返回值有可能为null,如果为null就意味着不能将源文件转为Movie对象。 

其它几个方法都是与控制GIF播放有关的。GIF播放起来就跟小电影差不多,有起始时间和时长,每一个时间点GIF呈现不同的图案,因此才会产生一个动图的效果。Movie中的duration方法就是获得该GIF动画的时长,setTime就是获得在某一时间点上GIF呈现的图案,而draw方法就是将GIF图案画到canvas上。因此使用控件来播放GIF的思路就是不断的调用Movie类的setTime方法和draw方法,将GIF图的每一个时间点上图案依次画到控件中。

接下来,对圆形图片的实现采用了两种方式,一种是使用着色器BitmapShader,另一种是使用混合图像PorterDuffXferMode。

着色器顾名思义就是给目标图像上色用的,BitmapSharder是Shader的一种,它在给目标图像上“色”的时候并不是使用颜色而是使用一个Bitmap。

图像混合模式就是当两幅图像交叠时在其交叠区域产生不同的效果,安卓一共提供了18种不同的混合效果,每种效果都有其应用的场合。

接下来就来写一个自定义View能显示圆形图片和GIF图片,同时它也应该能够以普通的方式来显示图片,因此我们的自定义View继承自ImageView,在ImageView的基础上做一些扩展和改写即可:

自定义View起名为GifView,继承ImageView

public class GifView extends ImageView

类中声明几个属性

private Movie gifPic;//GIF图片
private long gifStrat;//GIF图片开始播放的时间点
private int gifWidth,gifHeight;//GIF图片的宽、高
private boolean auto_playing;//是否运行GIF图片自动播放。该属性写入了attrs.xml,可以在布局文件中设置
private Bitmap playButton;//如果不允许GIF图片自动播放,会绘制一个播放按钮
private boolean isPlaying = false;//GIF图片是否正在播放


GifView的构造器

	public GifView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		int resId = getResourceId(context,attrs);
		if(resId!=0){
			//以流的形式获得resId对应的资源内容
			InputStream in = getResources().openRawResource(resId);
			gifPic = Movie.decodeStream(in);
			if(gifPic!=null){//如果gifPic为null,说明无法将内容解析为Movie格式,源图像可能不是GIF格式
				Options opts = new Options();
				opts.inJustDecodeBounds = true;
				BitmapFactory.decodeStream(in,null,opts );
				//获得GIF的宽和高,显示时会按照GIF图片本身的大小来设置控件的大小并显示
				gifWidth = opts.outWidth;
				gifHeight = opts.outHeight;
			}
		}
	}


其中getResourceId方法是一个自定义的方法,它的作用是获得使用者在布局文件中指定的src属性对应的图片id是什么,以及获取auto_play的值是什么

private int getResourceId(Context context, AttributeSet attrs) {
		TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.GifView);
		try {
			//auto_playing默认值为true
			auto_playing = t.getBoolean(R.styleable.GifView_auto_play, true);
			if(!auto_playing){//如果用户显式的指定了不允许自动播放,则获得一个播放按钮,在onDraw方法中绘制到控件上
				playButton = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_media_play);
				//为控件设置监听器,点击控件时设置isPlaying为true,并要求重绘
				setOnClickListener(new OnClickListener() {
					@Override
					public void onClick(View v) {
						if(v.getId()==getId()){
							isPlaying = true;
							invalidate();
						}
					}
				});
			}
			//布局文件中src属性指定的内容会赋值给ImageView控件的mValue属性
			//通过反射获得该属性值进而获得图像锁对应的资源id
			Field f = t.getClass().getDeclaredField("mValue");
			f.setAccessible(true);
			TypedValue typedValue = (TypedValue) f.get(t);
			return typedValue.resourceId;
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			t.recycle();
		}
		return 0;
	}

以上是各种准备工作,接下来就是onMeasure方法和onDraw方法

	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		if(gifPic!=null){
			//如果gifPic不为null,则将控件的尺寸设置为GIF图片尺寸的大小
			setMeasuredDimension(gifWidth, gifHeight);
		}
	}

	protected void onDraw(Canvas canvas) {
		if(gifPic == null){
			super.onDraw(canvas);
			return;
		}

		if(!auto_playing){//如果不允许自动播放
			if(!isPlaying){//如果GIF尚未开始播放
				gifPic.setTime(0);//取GIF图片的第一帧,通过canvas画到控件中显示
				gifPic.draw(canvas, 0, 0);
				//随后在空间的中心位置绘制一个播放按钮,点击按钮才允许播放
				canvas.drawBitmap(playButton, getWidth()/2-playButton.getWidth()/2, getHeight()/2-playButton.getHeight()/2, null);
				return;
			}
		}
		playGif(canvas);
		invalidate();
	}

onDraw方法中的playGif方法就是用来“播放”GIF动画的方法

	/**
	 * 用来“播放”GIF图
	 * 播放的实质就是将本次调用playGif方法的时间对应的GIF图中的那一帧
	 * 通过canvas绘制到控件中
	 * @param canvas
	 */
	private void playGif(Canvas canvas) {
		long now = SystemClock.uptimeMillis();
		if(gifStrat==0){
			gifStrat=now;
		}
		int duration = gifPic.duration();
		long span = now-gifStrat;
		//如果用户设置了不自动播放,在GIF完整播放完一遍后就停止播放
		if(!auto_playing&&span>=duration){
			isPlaying = false;
			//这句别忘了,一定要设置,否则再次点击播放按钮的时候,GIF图就无法播放了
			//不重置为0,一播放span会瞬间大于duration的
			gifStrat=0;
		}
		//将一帧帧的图像通过canvas绘制到控件中
		int time = (int)(span%duration);
		gifPic.setTime(time);
		gifPic.draw(canvas, 0, 0);
	}

还可以提供一个setGif方法,这样就允许使用者用代码的方式来指定要显示的GIF图

	/**
	 * 当获得GifView对象后,可以通过该方法设置让GifView显示的GIF图
	 * @param resId 要显示的GIF图资源ID
	 */
	public void setGif(int resId){
		InputStream is = getResources().openRawResource(resId);
		gifPic = Movie.decodeStream(is);
		auto_playing = true;
		gifWidth = gifPic.width();
		gifHeight = gifPic.height();
		//要求重新测量并重绘
		requestLayout();
	}

通过以上的代码可以使用GifView来播放GIF动图了。

接下来再为GifView提供两个使用圆形图片的方法,这两种方法表现效果是一样的,具体应用时愿意使用哪种均可以。

	/**
	 * 利用BitmapShader画圆形图
	 * @param bm 源图
	 */
	public void setCircleImageBitmap(Bitmap bm){
		//创建一个源图一样大小的空白图
		Bitmap result = Bitmap.createBitmap(bm.getWidth(),bm.getHeight(),bm.getConfig());
		//利用源图创建一个着色器
		BitmapShader shader = new BitmapShader(bm, TileMode.CLAMP,  TileMode.CLAMP);
		int size = Math.min(result.getWidth(), result.getHeight());
		Canvas c = new Canvas(result);
		Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
		p.setShader(shader);
		//利用着色器为空白图"贴"上一个"色",此时"色"是一副Bitmap图片
		c.drawCircle(size/2,size/2, size/2, p);
		//让图像以控件的大小来进行缩放
		setScaleType(ScaleType.FIT_XY);
		//调用ImageView的setImageBitmap图像真正将图像绘制到控件上
		super.setImageBitmap(result);
	}

	 * 利用混合模式画圆形图
	 * @param bm 源图
	 */
	public void setCircleImageBitmap2(Bitmap bm){
		Bitmap result = Bitmap.createBitmap(bm.getWidth(),bm.getHeight(),bm.getConfig());
		int size = Math.min(result.getWidth(), result.getHeight());
		Canvas c = new Canvas(result);
		Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
		//先在result上换一个黑色的圆形
		c.drawCircle(size/2,size/2, size/2, p);
		p.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
		//将源图绘制到result上,画笔设置了混合模式,源图和result交叠的部分会显示源图的内容,其余部分都不显示
		c.drawBitmap(bm, 0,0,p);
		//让图像以控件的大小来进行缩放
		setScaleType(ScaleType.FIT_XY);
		//调用ImageView的setImageBitmap图像真正将图像绘制到控件上
		super.setImageBitmap(result);
	}

着色器和混合模式都是非常强大的工具,可以通过它们设计出非常丰富多彩的图形图像效果。可以在此基础上做进一步的扩展!

贴一下attrs.xml文件的内容

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="GifView">
        <attr name="auto_play" format="boolean"></attr>
    </declare-styleable>
</resources>
如果希望为圆形图片加上外框,可以在attrs里面继续添加border_size、border_color这样的自定义属性,然后在GifView的构造器(或者getResourceId方法中)获得这些自定义属性的值或默认值,在setCircleImageBitmap/ setCircleImageBitmap2方法中,获得圆形图片后,设定画笔为STROKE,并设置边框的颜色与粗度,绘制边框即可。

		paint.setStyle(Style.STROKE);
		paint.setColor(bordercolor);
		paint.setStrokeWidth(borderwidth);
		canvas.drawCircle(size/2, size/2, size/2, paint);

最后就是使用GifView,布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:myapp="http://schemas.android.com/apk/res/com.example.gifview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin" >

    <com.example.gifview.GifView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginTop="10dp"
        android:contentDescription="@null"
        myapp:auto_play="false"
        android:src="@drawable/a" />

    <com.example.gifview.GifView
        android:id="@+id/gview"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginTop="10dp"
        android:contentDescription="@null"
        android:src="@drawable/ic_launcher" />

    <com.example.gifview.GifView
        android:id="@+id/gview2"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginTop="10dp"
        android:contentDescription="@null" />

</LinearLayout>

在MainActivity中对GifView的使用:

public class MainActivity extends Activity {
	GifView gview,gview2;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		gview = (GifView) findViewById(R.id.gview);
		gview2 = (GifView) findViewById(R.id.gview2);
		Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.cc2);
		gview.setCircleImageBitmap(bm );
		gview2.setGif(R.drawable.a);
	}
}
最后是效果图:

1)是不允许自动播放时,需要点击可以播放一次

2)是使用了着色器绘制的圆形图片

3)是利用setGif方法设置要显示的GIF图


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值