自定義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呼叫,外部呼叫,會測量不準。