Android自定义组件系列【8】——遮罩文字动画

遮罩文字的动画我们在Flash中非常常见,作为Android的应用开发者你是否也想将这种动画做到你的应用中去呢?这一篇文章我们来看看如何自定义一个ImageView来实现让一张文字图片实现文字的遮罩闪烁效果,下面先来看看效果吧。

(录屏幕延时导致效果看起来不是很好)

一、实现原理

  实现原理是重写View的onCreate方法,获取图片资源后对每个像素的透明度进行修改来实现,再启动一个线程来循环改变某个区域中的像素透明度。

RGBA基础知识:(下面几段介绍文字引用自维基百科)

  RGBA是代表Red(红色)Green(绿色)Blue(蓝色)和Alpha的色彩空间。虽然它有的时候被描述为一个颜色空间,但是它其实仅仅是RGB模型的附加了额外的信息。采用的颜色是RGB,可以属于任何一种RGB颜色空间,但是CatmullSmith在1971至1972年间提出了这个不可或缺的alpha数值,使得alpha渲染alpha合成变得可能。提出者以alpha来命名是源于经典的线性插值方程αA + (1-α)B所用的就是这个希腊字母

  alpha通道一般用作不透明度参数。如果一个像素的alpha通道数值为0%,那它就是完全透明的(也就是看不见的),而数值为100%则意味着一个完全不透明的像素(传统的数字图像)。在0%和100%之间的值则使得像素可以透过背景显示出来,就像透过玻璃(半透明性),这种效果是简单的二元透明性(透明或不透明)做不到的。它使数码合成变得容易。alpha通道值可以用百分比、整数或者像RGB参数那样用0到1的实数表示。

  有时它也被写成ARGB(像RGBA一样,但是第一个数据是alpha),是Macromedia的产品使用的术语。比如,0x80FFFF00是50%透明的黄色,因为所有的参数都在0到255的范围内表示。0x80是128,大约是255的一半。

PNG是一种使用RGBA的图像格式。

二、具体实现

package com.example.helloworld;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
/**
 * @author 阳光小强
 *
 */
public class SplashImageView extends ImageView{
    private Bitmap alterBitmap;
    private Canvas canvas;
    private Paint paint;
	private Handler handler;
	private static int START_POSITION = 20;
	private final int speed;
	private int nowPosition = START_POSITION;
	private static int SHOW_WIDTH = 20;
	private boolean isFirst = true;
	private boolean isStop = false;

	private class MyHandler extends Handler {

		private static final long SCALE = 10000;
		private static final int MSG_PAINT = 1;

		private final SplashImageView owner;
		private final int speed;

		private long angle;
		private long lastTime;
		public MyHandler(SplashImageView owner) {
			this.owner = owner;
			this.lastTime = SystemClock.elapsedRealtime();
			this.speed = owner.speed;
			sendEmptyMessage(MSG_PAINT);
		}

		@Override
		public void handleMessage(Message msg) {
			if (msg.what == MSG_PAINT) {
				long now = SystemClock.elapsedRealtime();
				long delta_time = now - lastTime;
				System.out.println("delta_time = " + delta_time);
				System.out.println("alterBitmap.Width = " + alterBitmap.getWidth());
				if(nowPosition + speed >= alterBitmap.getWidth() - START_POSITION - SHOW_WIDTH){
					if(isStop){
						handler.removeCallbacksAndMessages(null);
						handler = null;
						isStop = false;
						return;
					}else{
						nowPosition = START_POSITION;
					}
				}
				nowPosition = nowPosition + speed;
				if (delta_time > 0) {
					if(!notifiDraw(nowPosition)){
						return;
					}
				}
				this.sendEmptyMessageDelayed(MSG_PAINT, 10);
			}
		}
	}

	private boolean notifiDraw(long position) {
		System.out.println("nofityDrawToatal = " + position);
		if(position < alterBitmap.getWidth() - START_POSITION - SHOW_WIDTH){
			this.invalidate();
			return true;
		}
		if (handler != null) {
			handler.removeCallbacksAndMessages(null);
			handler = null;
		}
		return false;
	}

	@Override
	public void setVisibility(int visibility) {
		super.setVisibility(visibility);
		if(visibility == View.VISIBLE){
			if(handler == null){
	        	handler =  new MyHandler(this);
	        }else{
	        	handler.removeCallbacksAndMessages(null);
	        	handler.sendEmptyMessage(MyHandler.MSG_PAINT);
	        }
		}else{
			if(handler != null){
				handler.removeCallbacksAndMessages(null);
				handler = null;
			}
		}
	}

	public void stopSplashAnimation(){
		if(handler != null){
			isStop = true;
		}
	}

	public SplashImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FuseImageView, 0, 0);
		int resId = a.getResourceId(R.styleable.FuseImageView_imageSrc, 0);
		int speed = a.getInt(R.styleable.FuseImageView_speed, 5);
		this.speed = speed <= 0 ? 1 : speed;
		Bitmap up = BitmapFactory.decodeResource(context.getResources(), resId);
        alterBitmap = Bitmap.createBitmap(up.getWidth(), up.getHeight(), up.getConfig());  

        canvas = new Canvas(alterBitmap);
        paint = new Paint();
        paint.setStrokeWidth(5);
        paint.setColor(Color.BLACK);
        canvas.drawBitmap(up, new Matrix(), paint);  

        setImageBitmap(alterBitmap);  

        if(getVisibility() == View.VISIBLE){
        	if(handler == null){
	        	handler =  new MyHandler(this);
	        }
        }
	}

	@Override
	protected void onDraw(Canvas canvas) {

		super.onDraw(canvas);

		if(isFirst){
			isFirst = false;
			 for(int i=nowPosition; i<alterBitmap.getWidth() ; i++){
	             for(int j=0; j<alterBitmap.getHeight(); j++){
	                 int color = alterBitmap.getPixel(i, j);
	                 int r = Color.red(color);
	                 int g = Color.green(color);
	                 int b = Color.blue(color);
	                 int a = Color.alpha(color);
	                 if( a > 200){
	                	 color = Color.argb(80, r, g, b);
	                 }else{
	                	 color = Color.argb(a, r, g, b);
	                 }
	                 alterBitmap.setPixel(i, j, color);
	             }  

	         }
		}

		 for(int i=nowPosition; i<nowPosition + SHOW_WIDTH ; i++){
             for(int j=0; j<alterBitmap.getHeight(); j++){
                 int color = alterBitmap.getPixel(i, j);
                 int r = Color.red(color);
                 int g = Color.green(color);
                 int b = Color.blue(color);
                 int a = Color.alpha(color);
                 if(a == 80){
                	 color = Color.argb(255, r, g, b);
                 }else{
                	 color = Color.argb(a, r, g, b);
                 }
                 alterBitmap.setPixel(i, j, color);
             }  

         }  

		 if(nowPosition > START_POSITION){
			 for(int i= nowPosition - SHOW_WIDTH; i<nowPosition; i++){
	             for(int j=0; j<alterBitmap.getHeight(); j++){
	                 int color = alterBitmap.getPixel(i, j);
	                 int r = Color.red(color);
	                 int g = Color.green(color);
	                 int b = Color.blue(color);
	                 int a = Color.alpha(color);
	                 if( a > 200){
	                	 color = Color.argb(80, r, g, b);
	                 }else{
	                	 color = Color.argb(a, r, g, b);
	                 }
	                 alterBitmap.setPixel(i, j, color);
	             }  

	         }
		 }
        setImageBitmap(alterBitmap);
	}

}

三、实现详解

1、构造方法中进行初始化操作

	public SplashImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.FuseImageView, 0, 0);
		int resId = a.getResourceId(R.styleable.FuseImageView_imageSrc, 0);
		int speed = a.getInt(R.styleable.FuseImageView_speed, 5);
		this.speed = speed <= 0 ? 1 : speed;
		Bitmap up = BitmapFactory.decodeResource(context.getResources(), resId);
		alterBitmap = Bitmap.createBitmap(up.getWidth(), up.getHeight(),
				up.getConfig());

		canvas = new Canvas(alterBitmap);
		paint = new Paint();
		paint.setStrokeWidth(5);
		paint.setColor(Color.BLACK);
		canvas.drawBitmap(up, new Matrix(), paint);

		setImageBitmap(alterBitmap);

		if (getVisibility() == View.VISIBLE) {
			if (handler == null) {
				handler = new MyHandler(this);
			}
		}
	}

上面的TypedArray是自定义的属性,在res/values目录下新建一个attrs.xml添加自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="FuseImageView">
        <attr name="imageSrc" format="reference" />
        <attr name="speed" format="integer" />
    </declare-styleable>
</resources>

这里是自定义的两个属性,一个是图片资源ID另一个是遮罩移动速度(其实上面也可以继承自View来实现自定义,我这里是有特殊需要才继承自ImageView的)

然后通过BitmapFactory获取图片资源,并通过createBitmap方法创建一个可写的Bitmap资源给画布(Canvas),将可写的Bitmap绘制到同样资源的背景上。

底下的判读View是否可看见,是用来判读View是否可见,如果可见才开启线程进行动画的,不然的话开启线程绘制会浪费资源的(因为它根本就看不见)。

2、如何改变透明度并且绘制(onDraw方法)

		for (int i = nowPosition; i < nowPosition + SHOW_WIDTH; i++) {
			for (int j = 0; j < alterBitmap.getHeight(); j++) {
				int color = alterBitmap.getPixel(i, j);
				int r = Color.red(color);
				int g = Color.green(color);
				int b = Color.blue(color);
				int a = Color.alpha(color);
				if (a == 80) {
					color = Color.argb(255, r, g, b);
				} else {
					color = Color.argb(a, r, g, b);
				}
				alterBitmap.setPixel(i, j, color);
			}

		}

		if (nowPosition > START_POSITION) {
			for (int i = nowPosition - SHOW_WIDTH; i < nowPosition; i++) {
				for (int j = 0; j < alterBitmap.getHeight(); j++) {
					int color = alterBitmap.getPixel(i, j);
					int r = Color.red(color);
					int g = Color.green(color);
					int b = Color.blue(color);
					int a = Color.alpha(color);
					if (a > 200) {
						color = Color.argb(80, r, g, b);
					} else {
						color = Color.argb(a, r, g, b);
					}
					alterBitmap.setPixel(i, j, color);
				}

			}
		}

主要是上面两个循环来实现绘制的,上面的循环是绘制一块区域来将文字的透明度调为最小(255),这一部分的文字就显示为高亮了,其余部分的文字透明度调值调为80,就会显示背景颜色,文字的暗度就会下降。

3、如何循环移动遮罩

	private class MyHandler extends Handler {

		private static final long SCALE = 10000;
		private static final int MSG_PAINT = 1;

		private final SplashImageView owner;
		private final int speed;

		private long angle;
		private long lastTime;

		public MyHandler(SplashImageView owner) {
			this.owner = owner;
			this.lastTime = SystemClock.elapsedRealtime();
			this.speed = owner.speed;
			sendEmptyMessage(MSG_PAINT);
		}

		@Override
		public void handleMessage(Message msg) {
			if (msg.what == MSG_PAINT) {
				long now = SystemClock.elapsedRealtime();
				long delta_time = now - lastTime;
				System.out.println("delta_time = " + delta_time);
				System.out.println("alterBitmap.Width = "
						+ alterBitmap.getWidth());
				if (nowPosition + speed >= alterBitmap.getWidth()
						- START_POSITION - SHOW_WIDTH) {
					if (isStop) {
						handler.removeCallbacksAndMessages(null);
						handler = null;
						isStop = false;
						return;
					} else {
						nowPosition = START_POSITION;
					}
				}
				nowPosition = nowPosition + speed;
				if (delta_time > 0) {
					if (!notifiDraw(nowPosition)) {
						return;
					}
				}
				this.sendEmptyMessageDelayed(MSG_PAINT, 10);
			}
		}
	}

循环移动遮罩是写在一个线程中的,每隔10毫秒就去移动speed(配置的速度)的距离,来实现遮罩的移动效果,再取图片的宽度来判断是否已经到了最右边。


总结:其实上面的实现原理并不难,要点是要知道RGBA的知识和如何去改变像素的透明度。这个只是个人暂时想到的一个方法,如果有什么更好的方式实现,希望能交流一下。

另外“阳光小强”的另一篇博文《是男人就下100层【第三层】——高仿交通银行手机客户端界面》参加了CSDN举办的博文大赛,如果您觉得这些博文对您有帮助,希望您投出您宝贵的一票,投票地址:http://vote.blog.csdn.net/Article/Details?articleid=30101091

Android自定义组件系列【8】——遮罩文字动画,布布扣,bubuko.com

时间: 2024-08-02 06:59:16

Android自定义组件系列【8】——遮罩文字动画的相关文章

Android自定义组件系列【10】——随ViewPager滑动的导航条

昨天在用到ViewPager实现滑动导航的时候发现微信的导航条效果是跟随ViewPager的滑动而动的,刚开始想了一下,感觉可以使用动画实现,但是这个滑动是随手指时时变化的,貌似不可行,后来再网上搜了一下,找到一个开源代码,结果打开一看大吃一惊,这么简单的效果代码居然大概有300多行,太占手机存储空间了!后来自己干脆重写ViewGroup使用scrollTo方法实现了一下,具体实现过程如下: package com.example.slideupdownviewpage; import andr

Android自定义组件系列【9】——Canvas绘制折线图

有时候我们在项目中会遇到使用折线图等图形,Android的开源项目中为我们提供了很多插件,但是很多时候我们需要根据具体项目自定义这些图表,这一篇文章我们一起来看看如何在Android中使用Canvas绘制折线图.先看看绘制的效果: 实现原理很简单,我就直接给出代码: package com.example.testcanvasdraw; import java.util.ArrayList; import java.util.List; import java.util.Random; impo

Android自定义组件系列【6】——进阶实践(3)

上一篇<Android自定义组件系列[5]--进阶实践(2)>继续对任老师的<可下拉的PinnedHeaderExpandableListView的实现>进行了分析,这一篇计划中间插一段"知识点",对Android中的事件分发机制进行解析.细心的朋友可能会发现,打开大牛写的Android项目,里面很多组件都是自定义的(这就是为什么界面和体验这么吸引你的原因),但是要灵活的去自定义组件就必须对手势(也就是各种监听)必须熟悉,能处理好事件之间的关系. 先看一段代码:

Android自定义组件系列【7】——进阶实践(4)

上一篇<>中补充了关于Android中事件分发的过程知识,这一篇我们接着来分析任老师的<可下拉的PinnedHeaderExpandableListView的实现>. 一.StickyLayout中的OnGiveUpTouchEventListener接口的作用是什么? public interface OnGiveUpTouchEventListener { public boolean giveUpTouchEvent(MotionEvent event); } 在Sticky

Android自定义组件系列【5】——进阶实践(1)

简介 项目开发中发现问题.解决问题这个过程中会出现很多问题,比如重复出现.某个问题的遗留,这些问题的本质就是设计模式.今天记录设计模式的知识点. 内容 在java以及其他的面向对象设计模式中,类与类之间主要有6种关系,他们分别是:依赖.关联.聚合.组合.继承.实现.它们的耦合度依次增强. 依赖关系:对于两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务时,这两个对象之间主要体现为依赖关系.关联关系:分为单向关联和双向关联.在java中,单向关联表现为:类A当中使用了

Android自定义组件系列【5】——进阶实践(2)

上一篇<Android自定义组件系列[5]--进阶实践(1)>中对任老师的<可下拉的PinnedHeaderExpandableListView的实现>前一部分进行了实现,这一篇我们来看看ExpandableListView的使用并实现剩下的部分. 原文出处:http://blog.csdn.net/singwhatiwanna/article/details/25546871 一.ExpandableListView的用法 ExpandableListView是ListView的

Android自定义组件系列【12】——非UI线程绘图SurfaceView

一.SurfaceView的介绍 在前面我们已经会自定义View,使用canvas绘图,但是View的绘图机制存在一些缺陷. 1.View缺乏双缓冲机制. 2.程序必须重绘整个View上显示的图片,比较耗资源. 3.非UI线程无法更新View组件,所以会占用主线程资源,当需要在主线程中处理逻辑的时候会很慢. 在Android中为我们提供了一个SurfaceView来替代View实现绘制图形,一般在游戏绘图方面应用较广,所以如果是比较复杂的绘图建议使用SurfaceView. 二.SurfaceV

Android自定义组件系列【14】——Android5.0按钮波纹效果实现

今天任老师发表了一篇关于Android5.0中按钮按下的波纹效果实现<Android L中水波纹点击效果的实现>,出于好奇我下载了源代码看了一下效果,正好手边有一个Nexus手机,我结合实际效果看了一下,发现有一些地方和实际效果稍有不同,参考任老师的博文实现简单实现了一个重写View组件的代码,将全部代码贴出,如果有什么问题或者更好的方式请指出,在此再次感谢任老师的这篇博文. 转载请说明出处:http://blog.csdn.net/dawanganban 顺便在这里拉一下票,如果你觉得这篇文

Android自定义组件系列【15】——四个方向滑动的菜单实现

今天无意中实现了一个四个方向滑动的菜单,感觉挺好玩,滑动起来很顺手,目前还没有见过这样的应用,以后能不能在应用中出现或者说有没有实用价值就不好说了,既然已经做出来了就贴出来让大家也玩弄一下,说不定对你有所启发. 一.效果演示 (说明:目前没有安装Android模拟器,制作的动态图片太卡了,就贴一下静态图片吧,实际效果可以下载源代码查看) (向上滑动) (向下滑动) (向左滑动) (向右滑动) 二.实现过程介绍 1.放置5个View (分别是上下左右中) @Override protected v