天天看點

Android 自定義控件之滾動字幕條

大家在看電視的時候經常看到電視的下面或者上面會出現滾動字幕,上面會顯示一些廣告,或者在銀行大堂會看見顯示屏上也有滾動字幕顯示一些銀行的利率資訊和公告等通知,那這樣的效果在Android中怎麼實作呢?今天就帶大家玩一玩這個滾動字幕的效果。

一、視訊動畫原理:

醫學證明人類具有“視覺暫留”的特性,人的眼睛看到一幅畫或一個物體後,在0.34秒内不會消失。利用這一原理,在一幅畫還沒有消失前播放下一幅畫,就會給人造成一種流暢的視覺變化效果。連續的圖像變化每秒超過24幀(frame)畫面以上時,根據視覺暫留原理,人眼無法辨識單幅的靜态畫面;看上去是平滑連續的視覺效果。

換言之,那些視訊,動畫,電影電視,就是由很多張靜态圖檔連續快速播放的效果。那我們實作這個滾動字幕的原理就是快速連續地把一段文字畫在不同的位置,就會讓人看起來覺得文字在滾動。

二、效果圖:

Android 自定義控件之滾動字幕條

三、分三步走:

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>
           

源碼下載下傳:http://download.csdn.net/detail/u012604745/9871277