大家在看电视的时候经常看到电视的下面或者上面会出现滚动字幕,上面会显示一些广告,或者在银行大堂会看见显示屏上也有滚动字幕显示一些银行的利率信息和公告等通知,那这样的效果在Android中怎么实现呢?今天就带大家玩一玩这个滚动字幕的效果。
一、视频动画原理:
医学证明人类具有“视觉暂留”的特性,人的眼睛看到一幅画或一个物体后,在0.34秒内不会消失。利用这一原理,在一幅画还没有消失前播放下一幅画,就会给人造成一种流畅的视觉变化效果。连续的图像变化每秒超过24帧(frame)画面以上时,根据视觉暂留原理,人眼无法辨别单幅的静态画面;看上去是平滑连续的视觉效果。
换言之,那些视频,动画,电影电视,就是由很多张静态图片连续快速播放的效果。那我们实现这个滚动字幕的原理就是快速连续地把一段文字画在不同的位置,就会让人看起来觉得文字在滚动。
二、效果图:
三、分三步走:
1.自定义属性
2.自定义View
3.使用自定义View
四、第一步:自定义属性
在res --> values 下新建attrs.xml文件(有则不用新建),代码如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CustomScrollBar"> <attr name="clickEnable" format="boolean" /> <attr name="isHorizontal" format="boolean" /> <attr name="speed" format="integer" /> <attr name="text" format="string" /> <attr name="textColor" format="color" /> <attr name="textSize" format="dimension" /> <attr name="times" format="integer" /> </declare-styleable> </resources>
五、自定义View:
package com.dapeng.demo; /** * Created by dapeng on 2017/6/14. */ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.FontMetrics; import android.graphics.PixelFormat; import android.graphics.PorterDuff.Mode; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class CustomScrollBar extends SurfaceView implements SurfaceHolder.Callback { private final String TAG = "CustomScrollBar"; private SurfaceHolder holder; private Paint paint = null;// 画笔 private boolean bStop = false; // 停止滚动 private boolean clickEnable = false; // 可以点击 private boolean isHorizontal = true; // 水平|垂直 private int speed = 1; // 滚动速度 private String text = "";// 文本内容 private float textSize = 15f; // 字号 private int textColor = Color.BLACK; // 文字颜色 private int times = Integer.MAX_VALUE; // 滚动次数 private int viewWidth = 0;// 控件的长度 private int viewHeight = 0; // 控件的高度 private float textWidth = 0f;// 水平滚动时的文本长度 private float textHeight = 0f; // 垂直滚动时的文本高度 private float textX = 0f;// 文字的横坐标 private float textY = 0f;// 文字的纵坐标 private float viewWidth_plus_textLength = 0.0f;// 显示总长度 private int time = 0; // 已滚动次数 private ScheduledExecutorService scheduledExecutorService; // 执行滚动线程 public CustomScrollBar(Context context) { super(context); } public CustomScrollBar(Context context, AttributeSet attrs) { //---------1 super(context, attrs); holder = this.getHolder(); holder.addCallback(this); paint = new Paint(); //获取布局文件中的值 TypedArray arr = getContext().obtainStyledAttributes(attrs, R.styleable.CustomScrollBar); clickEnable = arr.getBoolean(R.styleable.CustomScrollBar_clickEnable, clickEnable); isHorizontal = arr.getBoolean(R.styleable.CustomScrollBar_isHorizontal, isHorizontal); speed = arr.getInteger(R.styleable.CustomScrollBar_speed, speed); text = arr.getString(R.styleable.CustomScrollBar_text); textColor = arr.getColor(R.styleable.CustomScrollBar_textColor, textColor); textSize = arr.getDimension(R.styleable.CustomScrollBar_textSize, textSize); times = arr.getInteger(R.styleable.CustomScrollBar_times, times); time = times; paint.setColor(textColor); paint.setTextSize(textSize); /* * 下面两行代码配合draw()方法中的canvas.drawColor(Color.TRANSPARENT,Mode.CLEAR); * 将画布填充为透明 */ setZOrderOnTop(true); getHolder().setFormat(PixelFormat.TRANSLUCENT); setFocusable(true); // 设置焦点 } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //----------2 super.onMeasure(widthMeasureSpec, heightMeasureSpec); viewWidth = MeasureSpec.getSize(widthMeasureSpec);//得到控件的宽度 viewHeight = MeasureSpec.getSize(heightMeasureSpec);//得到控件的高度 if (isHorizontal) { // 水平滚动 textWidth = paint.measureText(text);// 获取text的长度 viewWidth_plus_textLength = viewWidth + textWidth; textY = (viewHeight - getFontHeight(textSize)) / 2 + getPaddingTop() - getPaddingBottom(); } else { // 垂直滚动 textHeight = getFontHeight(textSize) * text.length();// 获取text的长度 viewWidth_plus_textLength = viewHeight + textHeight; textX = (viewWidth - textSize) / 2 + getPaddingLeft() - getPaddingRight(); } } @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { //----------4 Log.d(TAG, "surfaceChanged: "); } @Override public void surfaceCreated(SurfaceHolder holder) { //-----------3 bStop = false; scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService.scheduleAtFixedRate(new ScrollThread(), 1000, 10, TimeUnit.MILLISECONDS); Log.d(TAG, "surfaceCreated: "); } @Override public void surfaceDestroyed(SurfaceHolder arg0) { bStop = true; scheduledExecutorService.shutdown(); Log.d(TAG, "surfaceDestroyed: "); } // 获取字体高度 private int getFontHeight(float fontSize) { Paint paint = new Paint(); paint.setTextSize(fontSize); FontMetrics fm = paint.getFontMetrics(); return (int) Math.ceil(fm.descent - fm.ascent); } private void setTimes(int times) { if (times <= 0) { this.times = Integer.MAX_VALUE; } else { this.times = times; time = times; } } private void setText(String text) { this.text = text; } private void setSpeed(int speed) { if (speed > 10 || speed < 0) { throw new IllegalArgumentException("Speed was invalid integer, it must between 0 and 10"); } else { this.speed = speed; } } /** * 当屏幕被触摸时调用 */ @Override public boolean onTouchEvent(MotionEvent event) { if (!clickEnable) { return true; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: bStop = !bStop; if (!bStop && time == 0) { time = times; } break; } return true; } private synchronized void draw(float X, float Y) { Canvas canvas = holder.lockCanvas();//获取画布 canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);// 通过清屏把画布填充为透明 if (isHorizontal) { // 水平滚动 canvas.drawText(text, X, Y, paint); } else { // 垂直滚动 for (int i = 0; i < text.length(); i++) { canvas.drawText(text.charAt(i) + "", X, Y + (i + 1) * getFontHeight(textSize), paint); } } holder.unlockCanvasAndPost(canvas);// 解锁画布,提交画好的图像 } class ScrollThread implements Runnable { @Override public void run() { while (!bStop) { if (isHorizontal) { draw(viewWidth - textX, textY); textX += speed;// 速度设置:1-10 if (textX > viewWidth_plus_textLength) { textX = 0; --time; } } else { draw(textX, viewHeight - textY); textY += speed; if (textY > viewWidth_plus_textLength) { textY = 0; --time; } } if (time <= 0) { bStop = true; } } } } }
上面的代码注释的很清晰了,在此我再介绍两点:
1.SurfaceView:在Android系统中,有一种特殊的视图,称为SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行绘制。又由于不会占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应。所以SurfaceView很强大,一般用在游戏制作,视频播放等耗UI资源的开发上。
2.上面画文字的逻辑重点就在ScrollThread中,通过开启一个线程不断地循环对文字进行重绘,每一次循环通过改变textX和textY的值来改变文字的位置,先把画布清空,再重新进行绘制,就实现了文字不断滚动的效果。最后,大家要注意一下,canvas.drawText(text, X, Y, paint);这里的X,Y坐标是相对于本身的控件而言的,不是相对于整个屏幕,这样大家就能理解draw(viewWidth - textX, textY);这么写的原因了。
六、在布局中使用自定义View
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:dapeng="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.dapeng.demo.CustomScrollBar android:id="@+id/hor" android:layout_width="match_parent" android:layout_height="100dp" android:layout_alignParentBottom="true" android:layout_marginBottom="-40dp" android:background="#000000" dapeng:clickEnable="true" dapeng:isHorizontal="true" dapeng:speed="2" dapeng:text="dapeng dapeng" dapeng:textColor="#ffffff" dapeng:textSize="30sp" dapeng:times="3" /> <com.dapeng.demo.CustomScrollBar android:layout_width="100dp" android:layout_height="match_parent" android:layout_above="@id/hor" android:layout_centerHorizontal="true" android:background="#ffffff" dapeng:clickEnable="false" dapeng:isHorizontal="false" dapeng:speed="4" dapeng:text="君子生非异也,善假于物也。" dapeng:textColor="#000000" dapeng:textSize="30sp" /> </RelativeLayout>