2015年12月17日 星期四

Android Project 可控式跑馬燈 MarqueeText.java

基本說明

  • 寫一個 MarqueeText.java 之可控的跑馬燈,並於主頁面程式實作。
  • 撰寫編譯軟體:Android Studio 1.4
  • 版本:Android 5.1
  • 繼承 TextView 功能,另外增加可調速度、方向、暫停、角度。
  • 加入 SeekBar 拖動條來控制 Delay 量,達到即時改變跑馬燈的速度。
  • 可做成一個 Palette-CustomView 選項,可以直接加到 activity_main.xml 佈局內。
##ReadMore##
---

Step 1. 以開新專案 MarqueeDemo

---

Step 2. 新增一個 JAVA Class → MarqueeText.java

package com.example.lin.marqueedemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by lin on 2015/12/15.
 * 自製可調式跑馬燈(包含:速度、方向、暫停及繼承其他同 TextView 之功能。
 */
public class MarqueeText extends TextView implements Runnable {
    public enum MarqueeDirection {
        LEFT, RIGHT
    }
    private volatile boolean m_isStop = false;      // 跑馬燈是否 stop
    private boolean m_isMeasure = false;            // 跑馬燈初始值量測 flag (只做一次)
    private int m_TextWidth = 0;            // 內文長度
    private int m_Distance = 3;             // 跑馬燈每次跳動距離
    private int m_Scroll_X = 0;             // 跑馬燈 X 軸座標
    private int m_Scroll_Y = 0;             // 跑馬燈 Y 軸座標
    public int m_Delay = 50;                 // 跑馬燈速度(Delay 越小越快)
    public MarqueeDirection m_Direction = MarqueeDirection.LEFT;    // 跑馬燈方向控制

    public MarqueeText(Context context) {
        super(context);
    }

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

    public MarqueeText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    // 程式啟動做一次
    protected void onDraw(Canvas canvas) {
        if (!m_isMeasure) {
            setEllipsize(TextUtils.TruncateAt.MARQUEE);
            setSingleLine(true);
            getTextWidth();
            setScrollInit();
            startScroll();
            m_isMeasure = true;
        }
        super.onDraw(canvas);
    }

    // 量測 android:text 內文長度
    private void getTextWidth() {
        Paint paint = getPaint();
        String str = getText().toString();
        m_TextWidth = (int) paint.measureText(str);
    }

    // 跑馬燈起始位置 (RIGHT/LEFT 兩種不同)
    protected void setScrollInit() {
        // 從右向左滾動(跑馬燈方向 ←)
        if (m_Direction == MarqueeDirection.RIGHT) {
            m_Scroll_X = -getWidth();
        } else {
        // 從左向右滾動(跑馬燈方向 →)
            m_Scroll_X = m_TextWidth;
        }
        // 設定 TextView 內文滾動座標
        scrollTo(m_Scroll_X, m_Scroll_Y);
    }

    // 實作 Runnable 的 run() 方法,用做跑馬燈移動的座標累加/減
    @Override
    public void run() {
        // 偵測到 m_isStop=true,就做 return 來結束 run() 方法。
        if (m_isStop) {
            return;
        }

        // 偵測方向 m_Direction,做座標的累加/累減
        if (m_Direction == MarqueeDirection.RIGHT) {
            // 方向 ←
            m_Scroll_X += m_Distance;
            if (getScrollX() >= m_TextWidth) {
                setScrollInit();
            }
        } else {
            // 方向 →
            m_Scroll_X -= m_Distance;
            if (getScrollX() <= -getWidth()) {
                setScrollInit();
            }
        }
        // 設定 TextView 內文滾動座標
        scrollTo(m_Scroll_X, m_Scroll_Y);
        // 做 Delay
        postDelayed(this, m_Delay);
    }

    public void startScroll() {
        m_isStop = false;
        removeCallbacks(this);  // 清除執行緒
        post(this);             // 更新執行緒
    }

    public void pauseScroll() {
        m_isStop = true;
        removeCallbacks(this);  // 清除執行緒
        post(this);             // 更新執行緒
    }

    public void switchScroll() {
        // 切換跑馬燈方向
        if (m_Direction == MarqueeDirection.RIGHT) {
            m_Direction = MarqueeDirection.LEFT;
        } else {
            m_Direction = MarqueeDirection.RIGHT;
        }
    }

    public void setDelay(int d) {
        m_Delay = d;
        removeCallbacks(this);  // 清除執行緒
        post(this);             // 更新執行緒
    }
}
---

Step 3. 將寫好的跑馬燈加入佈局檔 activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:weightSum="1"
    android:background="#404040">

    <com.example.lin.marqueedemo.MarqueeText
        android:id="@+id/test"
        android:rotation="180"
        android:text="↖( ̄▽ ̄)↗ 聖誕節快樂 (ノ>▽<。)ノ"
        android:textSize="30dp"
        android:textColor="#ffffff"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"/>

    <com.example.lin.marqueedemo.MarqueeText
        android:id="@+id/test2"
        android:text="d(`・∀・)b  Merry Christmas 。:.゚ヽ(*′∀`)ノ゚.:。 "
        android:textSize="30dp"
        android:textColor="#e6da04"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"/>

        <LinearLayout
            android:orientation="vertical"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:layout_gravity="center"
            android:layout_centerInParent="true" >

            <LinearLayout
                android:orientation="horizontal"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:weightSum="1">

                <TextView
                    android:layout_width="60dp"
                    android:layout_height="wrap_content"
                    android:text="Delay 1:"
                    android:textColor="#FFFFFF"
                    android:textSize="15dp"
                    android:layout_gravity="left"
                    android:id="@+id/textView" />

                <SeekBar
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:id="@+id/seekBar"
                    android:progress="@integer/seekbarValue"
                    android:max="100" />



            </LinearLayout>

            <TextView
                android:layout_width="30dp"
                android:layout_height="20dp"
                android:text="@integer/seekbarValue"
                android:textColor="#FFFFFF"
                android:textSize="15dp"
                android:layout_gravity="right"
                android:id="@+id/textView2" />

            <Button
                android:onClick="btn_start"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:text="Start"
                android:id="@+id/button" />

            <Button
                android:onClick="btn_pause"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:text="Pause"
                android:id="@+id/button2" />

            <Button
                android:onClick="btn_init"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:text="Init Setting"
                android:id="@+id/button3" />

            <Button
                android:onClick="btn_switch"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:text="Switch Direction"
                android:id="@+id/button4" />

            <LinearLayout
                android:orientation="horizontal"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:weightSum="1" >

                <TextView
                    android:layout_width="60dp"
                    android:layout_height="wrap_content"
                    android:text="Delay 2:"
                    android:textColor="#FFFFFF"
                    android:textSize="15dp"
                    android:layout_gravity="left"
                    android:id="@+id/textView3" />

                <SeekBar
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:id="@+id/seekBar2"
                    android:progress="@integer/seekbarValue"
                    android:max="100" />
            </LinearLayout>

            <TextView
                android:layout_width="30dp"
                android:layout_height="20dp"
                android:text="@integer/seekbarValue"
                android:textColor="#FFFFFF"
                android:textSize="15dp"
                android:layout_gravity="right"
                android:id="@+id/textView4" />

        </LinearLayout>
</RelativeLayout>
---

Stop 4. 至 values 目錄新增一個 Values XML file → integers.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <integer name="seekbarValue">50</integer>
</resources>


Step 5. 撰寫頁面主程式 MainActivity.java

package com.example.lin.marqueedemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private MarqueeText test, test2;
    private TextView t1, t2, t3, t4;
    private SeekBar delayBar, delayBar2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initMarqueeText();
        initSeekBar();
    }

    public void initSeekBar() {
        delayBar = (SeekBar) findViewById(R.id.seekBar);
        delayBar2 = (SeekBar) findViewById(R.id.seekBar2);
        t1 = (TextView) findViewById(R.id.textView);
        t2 = (TextView) findViewById(R.id.textView2);
        t3 = (TextView) findViewById(R.id.textView3);
        t4 = (TextView) findViewById(R.id.textView4);

        delayBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                t2.setText(String.valueOf(progress));
                test.setDelay(Integer.valueOf(progress));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });

        delayBar2.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                t4.setText(String.valueOf(progress));
                test2.setDelay(Integer.valueOf(progress));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });

    }

    public void initMarqueeText() {
        test = (MarqueeText) findViewById(R.id.test);
        test2 = (MarqueeText) findViewById(R.id.test2);
        test.m_Direction = MarqueeText.MarqueeDirection.RIGHT;
        test2.m_Direction = MarqueeText.MarqueeDirection.RIGHT;
    }

    public void btn_start(View view1) {
        test.startScroll();
        test2.startScroll();
    }

    public void btn_pause(View view2) {
        test.pauseScroll();
        test2.pauseScroll();
    }

    public void btn_init(View view3) {
        test.setScrollInit();
        test2.setScrollInit();
        delayBar.setProgress(getResources().getInteger(R.integer.seekbarValue));
        delayBar2.setProgress(getResources().getInteger(R.integer.seekbarValue));
    }

    public void btn_switch(View view4) {
        test.switchScroll();
        test2.switchScroll();
    }

}

---
原始檔 GitLab 備份(限校內):http://120.117.72.71/fate615030/androidProject.MarqueeDemo.git

Reference:
http://www.inote.tw/android-marquee-example
http://www.cnblogs.com/nuliniaoboke/archive/2013/01/10/2854707.html