1. 程式人生 > >自定義View+動畫,實現單行文字滾動(非跑馬燈)

自定義View+動畫,實現單行文字滾動(非跑馬燈)

原型圖:
在這裡插入圖片描述

需求1:使用者看視訊的時候,暱稱從右到左飄過。

功能實現:因為暱稱不會太長,短文字是不能用跑馬燈的,跑步起來。除非自定義。那就用平移動畫。

需求2:飄的文字改了,後臺返回,可長可短,長文字可能幾十個字(文字長度超過螢幕寬度)。單行,長文字時不能換行。

需求2要實現,有3個關鍵詞要注意:飄(移動)、可長可短、單行。

我這裡直接說測試結果過:
1、不能用跑馬燈,因為短文字跑不起來
2、如果還是用TextView,設定單行,TextView的寬度是wrap_content,則會把文字截斷(不能設定成match_parent,否則,短文字時,樣式不滿足要求)。

那麼,就只能自定義,然後讓這個自定義View,做平移動畫。

思路:拿到文字,測量文字寬、高。然後通知系統,我們需要這麼寬、這麼高。然後,繪製,最後移動。

注意最後的說明!
注意最後的說明!
注意最後的說明!

雖然我上一篇部落格說過了,不過,關於文字,我還是想說一句,需要注意下圖:
在這裡插入圖片描述

原始碼:
自定義移動的TextView,因為僅僅是繪製文字,我就繼承View了

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class MoveTextView extends View {

    private TextPaint textPaint;
    private Context mContext;
    private float ascent;
    private float descent;
    private float textOffset;
    private float vWidth;
    private float vHeight;

    private String text;

    public MoveTextView(Context context) {
        this(context, null);
    }

    public MoveTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MoveTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();

    }

    private void init() {

        textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(50);
        textPaint.setColor(0xffffffff);
        textPaint.setTextAlign(Paint.Align.LEFT);
        //文字的上坡度和下坡度。用於計算偏移量
        ascent = textPaint.ascent();
        descent = textPaint.descent();

        //偏移量,用於輔助文字在豎直方向居中
        textOffset = (ascent + descent) / 2;

    }

    public void setMoveText(String str) {

        text = str;

        vWidth = textPaint.measureText(text) + getPaddingStart() + getPaddingEnd();
        vHeight = descent - ascent + getPaddingBottom() + getPaddingTop();

        Log.e("setMoveText", text);

        Log.e("getPaddingStart", getPaddingStart() + "");
        Log.e("getPaddingEnd", getPaddingEnd() + "");
        Log.e("getPaddingBottom", getPaddingBottom() + "");
        Log.e("getPaddingTop", getPaddingTop() + "");
        Log.e("ascent", ascent + "");
        Log.e("descent", descent + "");
        Log.e("vWidth", vWidth + "");
        Log.e("vHeight", vHeight + "");

        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //告訴系統,我這個控制元件,需要這麼寬,這麼高
        setMeasuredDimension((int) vWidth, (int) vHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        if (!TextUtils.isEmpty(text) && vHeight != 0) {
            canvas.drawText(text, getPaddingStart(), vHeight / 2 - textOffset, textPaint);
        }

    }
}

佈局檔案:

<?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"
    >

    <TextView
        android:id="@+id/start_move_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#55ff0000"
        android:gravity="center"
        android:padding="10dp"
        android:text="開始動畫"
        android:textSize="25sp"/>

    <com.chen.demo.MoveTextView
        android:id="@+id/move_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_below="@id/start_move_tv"
        android:layout_marginTop="10dp"
        android:background="#80000000"
        android:paddingBottom="5dp"
        android:paddingEnd="10dp"
        android:paddingStart="10dp"
        android:paddingTop="5dp"
        android:visibility="gone"/>

</RelativeLayout>

使用:

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.TextView;

public class MainActivity extends Activity {

    private TextView start_move_tv;

    private MoveTextView move_tv;

    private TranslateAnimation translateAnimation;

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

        start_move_tv= (TextView) findViewById(R.id.start_move_tv);
        move_tv= (MoveTextView) findViewById(R.id.move_tv);

        move_tv.setMoveText("CSDN (Chinese Software Developer Network) 創立於1999年,是中國最大的IT社群和服務平臺,為中國的軟體開發者和IT從業者提供知識傳播、職業發展、軟體開發等全生命週期服務,滿足他們在職業發展中學習及共享知識和資訊、建立職業發展社交圈、通過軟體開發實現技術商業化等剛性需求");

        start_move_tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (translateAnimation == null) {
                    translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_PARENT, -1f, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
                    //設定動畫時間         
                    translateAnimation.setDuration(15000);

                    translateAnimation.setInterpolator(new LinearInterpolator());
                    translateAnimation.setAnimationListener(new Animation.AnimationListener() {
                        @Override
                        public void onAnimationStart(Animation animation) {

                            move_tv.setVisibility(View.VISIBLE);
                        }

                        @Override
                        public void onAnimationEnd(Animation animation) {

                            move_tv.setVisibility(View.INVISIBLE);

                        }

                        @Override
                        public void onAnimationRepeat(Animation animation) {

                        }
                    });
                }
                move_tv.setVisibility(View.VISIBLE);
                move_tv.startAnimation(translateAnimation);

            }
        });

    }

}

附日誌:

11-21 10:20:01.489 16798-16798/com.chen.demo E/setMoveText: CSDN (Chinese Software Developer Network) 創立於1999年,是中國最大的IT社群和服務平臺,為中國的軟體開發者和IT從業者提供知識傳播、職業發展、軟體開發等全生命週期服務,滿足他們在職業發展中學習及共享知識和資訊、建立職業發展社交圈、通過軟體開發實現技術商業化等剛性需求
11-21 10:20:01.489 16798-16798/com.chen.demo E/getPaddingStart: 30
11-21 10:20:01.489 16798-16798/com.chen.demo E/getPaddingEnd: 30
11-21 10:20:01.489 16798-16798/com.chen.demo E/getPaddingBottom: 15
11-21 10:20:01.489 16798-16798/com.chen.demo E/getPaddingTop: 15
11-21 10:20:01.489 16798-16798/com.chen.demo E/ascent: -52.148438
11-21 10:20:01.489 16798-16798/com.chen.demo E/descent: 13.28125
11-21 10:20:01.489 16798-16798/com.chen.demo E/vWidth: 6444.0
11-21 10:20:01.489 16798-16798/com.chen.demo E/vHeight: 95.42969

這是文字很長的模擬

說明:
1、細心是人可能發現了,我在佈局中建立MoveTextView的時候,是先GONE掉的。

android:visibility="gone"

這裡,就要說明一下,View的測量、繪製,我理解的是:可見–>測量、繪製。
用上面的demo解釋,就是:如果一開始就可見(Visible),findViewById後,就開始測量了,然後才去設定文字(setMoveText),因為是先測量,結果肯定是不準確的。所以,要先GONE掉,設定了文字,什麼的都準備好了,再Visible。(如果佈局中GONE掉,findViewById後Visible,然後去設定文字,也是不準的。總之,必須先設定文字,然後,才能測量)

2、可有人會說,就算Visible的第一次測量不準,既然setMoveText中拿到了文字的寬高,在這個方法裡去呼叫setMeasuredDimension方法,通知系統,不就行了?也沒有必要一開始GONE掉啊。我們去看一下setMeasuredDimension這個方法:原始碼中註釋說明是:

This method must be called by {@link #onMeasure(int, int)}
to store the measured width and measured height. Failing to do so will trigger an
exception at measurement time.

翻譯:
這個方法必須由{@link #onMeasure(int, int)}呼叫
來儲存測量寬度和測量高度。如果做不到這一點,就會引發
測量時異常。

說的很清楚,setMeasuredDimension必須由onMeasure呼叫,外部呼叫,會測量不準。