android 滚动字幕


最近项目需求做个循环滚动字幕功能,自己找了相关资料,分别实现了垂直滚动和水平滚动方式,根据自己的风格用两种方法实现了该功能;


2015-12-15更新:以前实现效果并不理想(文字的格式排版,自定义颜色,大小字号等TextView原有属性都不能很好兼容实现),在Github上找了一个较好的实现,并对此进行修改改进处理,这个支持所有TextView属性,还支持速度

实现原理:垂直滚动textview加ScrollView实现,水平滚动textview加HorizontalScrollView实现

具体实现:

import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.TextView;

public class TextMarqueeView {

	private Context mContext = null;
	private TextView txtView = null;
	private View vroot = null;

	private Animation mMoveTextOut = null;
	// private Animation mMoveTextIn = null;

	private Animation mMoveTextUp = null;
	// private Animation mMoveTextDown=null;
	private boolean mMarqueeNeeded = false;

	private static final String TAG = "TextMarqueeView112";
	private static final int TEXTVIEW_VIRTUAL_WIDTH = 990000;
	private static final int TEXTVIEW_HEIGHT = 990000;

	/** 由左到右滚动 */
	public static final int SCROLL_HORIZONTAL_LEFT = 111;
	/** 由下到上滚动 */
	public static final int SCROLL_VERTICAL_UP = 112;

	private int currentScrollDirection = SCROLL_HORIZONTAL_LEFT;

	public int getCurrentScrollDirection() {
		return currentScrollDirection;
	}

	public void setCurrentScrollDirection(int currentScrollDirection) {
		this.currentScrollDirection = currentScrollDirection;
	}

	/**
	 * Control the speed. The lower this value, the faster it will scroll.
	 */
	public static final int SPEED_NORMAL = 20;
	public static final int SPEED_FAST = 12;
	public static final int SPEED_LOW = 30;

	/**
	 * Control the pause between the animations. Also, after starting this
	 * activity.
	 */
	private static final int DEFAULT_ANIMATION_PAUSE = 1000;

	private int mSpeedHorizontal = SPEED_NORMAL;
	private int mSpeedVertical = SPEED_NORMAL * 2;

	private int mAnimationPause = DEFAULT_ANIMATION_PAUSE;

	private Interpolator mInterpolator = new LinearInterpolator();

	private boolean mCancelled = false;
	private Runnable mAnimationStartRunnable;

	private boolean mStarted = false;
	private int currentSrollMode = TextMarqueeView.SCROLL_HORIZONTAL_LEFT;

	public TextMarqueeView(Context context) {
		this.mContext = context;
		intView();
	}

	public void setTextSpeed(int speedMode) {
		if (speedMode == SPEED_FAST) {
			this.mSpeedHorizontal = SPEED_FAST;
			this.mSpeedVertical = SPEED_FAST * 2;
		} else if (speedMode == SPEED_LOW) {
			this.mSpeedHorizontal = SPEED_LOW;
			this.mSpeedVertical = SPEED_LOW * 2;
		} else {
			this.mSpeedHorizontal = SPEED_NORMAL;
			this.mSpeedVertical = SPEED_NORMAL * 2;
		}

	}

	private ITextPlayEnd endLis;

	public void setEndListner(ITextPlayEnd endLis) {
		this.endLis = endLis;
	}

	public interface ITextPlayEnd {
		public void playEnd();
	}


	private void intView() {

		LayoutInflater inflater = (LayoutInflater) mContext
				.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

		if (currentScrollDirection == TextMarqueeView.SCROLL_HORIZONTAL_LEFT) {
			vroot = inflater.inflate(R.layout.text_horizontal_scroll, null);
			txtView = (TextView) vroot.findViewById(R.id.textViewHorizontal);

		} else if (currentScrollDirection == TextMarqueeView.SCROLL_VERTICAL_UP) {
			vroot = inflater.inflate(R.layout.text_vertical_scroll, null);
			txtView = (TextView) vroot.findViewById(R.id.textViewVertical);
		}

	}

	protected View getCreateView() {
		return vroot;
	}

	protected void setTextContent(String txt) {
		txtView.setText(txt);
	}

	public TextView getElevatorWidgetView() {
		return txtView;
	}


	public void startMarquee() { // Log.d(TAG, "startMarquee()");
		if (this.currentSrollMode == TextMarqueeView.SCROLL_HORIZONTAL_LEFT) {
			if (mMarqueeNeeded && mMoveTextOut != null)
				startHorizontalAnimation();
		} else if (this.currentSrollMode == TextMarqueeView.SCROLL_VERTICAL_UP) {
			if (mMarqueeNeeded && mMoveTextUp != null)
				startVerticalAnimation();
		}

		mCancelled = false;
		mStarted = true;
	}

	private void startHorizontalAnimation() {

		mAnimationStartRunnable = new Runnable() {
			@Override
			public void run() {
				Log.d(TAG, "startHorizontalAnimation");
				txtView.startAnimation(mMoveTextOut);
			}
		};
		vroot.postDelayed(mAnimationStartRunnable, mAnimationPause);
	}

	private void startVerticalAnimation() {

		mAnimationStartRunnable = new Runnable() {
			@Override
			public void run() {
				Log.d(TAG, "startVerticalAnimation");
				txtView.startAnimation(mMoveTextUp);
			}
		};
		vroot.postDelayed(mAnimationStartRunnable, mAnimationPause);
	}

	/**
	 * Disables the animations.
	 */
	public void reset() {
		mCancelled = true;

		if (mAnimationStartRunnable != null) {
			vroot.removeCallbacks(mAnimationStartRunnable);
		}

		txtView.clearAnimation();
		mStarted = false;

		if (mMoveTextOut != null)
			mMoveTextOut.reset();
		if (mMoveTextUp != null)
			mMoveTextUp.reset();

		// mAnimationStartRunnable=null;
		vroot.invalidate();
	}

	private void prepareAnimation() {

		String text = txtView.getText().toString(); // Log.d(TAG,
													// "text content="+text);
		if (TextUtils.isEmpty(text))
			return;
		Paint mPaint = txtView.getPaint();

		mPaint.setTextSize(txtView.getTextSize());
		mPaint.setTypeface(txtView.getTypeface());
		Rect mBounds = new Rect();
		mPaint.getTextBounds(text, 0, text.length(), mBounds);
		float textWidth = mBounds.width();
		float textHeight = mBounds.height();
		// Log.d(TAG, "Rect calucate w h="+textWidth+" :"+textHeight);
		// Log.d(TAG, "measuredWidth    : " +
		// vroot.getMeasuredWidth()+" measuredHeight :"+vroot.getMeasuredHeight());
		float padTextWidth = mPaint.measureText(text, 0, 1);

		/*
		 * float mTextWidth =
		 * mPaint.measureText(mTextField.getText().toString()); Log.d(TAG,
		 * "mTextWidth       : " + mTextWidth);
		 */

		if (currentSrollMode == TextMarqueeView.SCROLL_HORIZONTAL_LEFT) {
			mMarqueeNeeded = textWidth > vroot.getMeasuredWidth();
			// 垂直居中坐标
			int centerPosY = Math.round(vroot.getMeasuredHeight() / 2
					- textHeight / 2);
			int duration = (int) Math.ceil(textWidth * mSpeedHorizontal);
			initHorizontalAnim(vroot.getMeasuredWidth() - padTextWidth,
					-(textWidth + padTextWidth), centerPosY, duration);

			// Log.d(TAG,
			// "get w h left top="+vroot.getWidth()+" "+vroot.getHeight()+" "+vroot.getLeft()+" "+vroot.getTop());
			// initHorizontalAnim(getLeft()+getMeasuredWidth()/2,getLeft(),8*1000);
		} else if (currentSrollMode == TextMarqueeView.SCROLL_VERTICAL_UP) {
			// int lines=txtView.getLineCount();
			int lines = (int) Math.ceil(textWidth / vroot.getMeasuredHeight());
			mMarqueeNeeded = lines * textHeight > vroot.getMeasuredHeight();// mTextField.getHeight();
			
			// Log.d(TAG, "verticalView line count="+lines);
			// Log.d(TAG,
			// " getView w h"+txtView.getWidth()+", "+txtView.getHeight());
			// 滚动完最后一行
			// lines++;
			float moveHeight = lines * textHeight;
			int duration = (int) Math.ceil(moveHeight * mSpeedVertical);
			// Log.d(TAG, "moveDistance="+moveHeight);
			initVerticalAnim(vroot.getMeasuredHeight() + textHeight * 2,
					-(moveHeight - textHeight * 3), duration);//
		}
	}

	private void initHorizontalAnim(float fromX, float toX, float posY,
			int duration) {// ,float locationY
	// Log.d(TAG, "initHorizontalAnim fromx="+fromX+" toX="+toX);

		mMoveTextOut = new TranslateAnimation(fromX, toX, posY, posY);
		mMoveTextOut.setDuration(duration);
		mMoveTextOut.setInterpolator(mInterpolator);
		mMoveTextOut.setFillAfter(true);

		mMoveTextOut.setAnimationListener(new Animation.AnimationListener() {
			@Override
			public void onAnimationStart(Animation animation) { // Log.d(TAG,
																// "mMoveTextOut_onAnimationStart");
				expandHorizontalTextView();
			}

			@Override
			public void onAnimationEnd(Animation animation) { // Log.d(TAG,
																// "mMoveTextOut_animEnd");
				cutHorizontalTextView();
				if (endLis != null)
					endLis.playEnd();

				if (mCancelled) {
					return;
				}
				startHorizontalAnimation();
			}

			@Override
			public void onAnimationRepeat(Animation animation) {
			}
		});

	}

	private void initVerticalAnim(float fromY, float toY, int duration) {
		mMoveTextUp = new TranslateAnimation(0, 0, fromY, toY);
		mMoveTextUp.setDuration(duration);
		mMoveTextUp.setInterpolator(mInterpolator);
		mMoveTextUp.setFillAfter(true);

		mMoveTextUp.setAnimationListener(new Animation.AnimationListener() {
			@Override
			public void onAnimationStart(Animation animation) {// Log.d(TAG,
																// "mMoveTextUp_onAnimationStart");
				expandVerticalTextView();
			}

			@Override
			public void onAnimationEnd(Animation animation) { // Log.d(TAG,
																// "mMoveTextUp_AnimEnd");
				cutVerticalTextView();
				if (endLis != null)
					endLis.playEnd();

				if (mCancelled) {
					return;
				}
				startVerticalAnimation();
			}

			@Override
			public void onAnimationRepeat(Animation animation) {
			}
		});

	}

	private void initHorizontalView() {
		txtView.addTextChangedListener(new TextWatcher() {
			@Override
			public void beforeTextChanged(CharSequence charSequence, int i,
					int i2, int i3) {
			}

			@Override
			public void onTextChanged(CharSequence charSequence, int i, int i2,
					int i3) {
			}

			@Override
			public void afterTextChanged(Editable editable) { // Log.d(TAG,// "afterTextChanged");
				
				final boolean continueAnimation = mStarted;

				reset();
				prepareAnimation();
				cutHorizontalTextView();

				/*
				 * if (continueAnimation) { Log.d(TAG,
				 * "TextChange Restart play"); startMarquee(); }
				 */
				vroot.post(new Runnable() {
					@Override
					public void run() {
						if (continueAnimation) { // Log.d(TAG,
													// "TextChange Restart play");
							startMarquee();
						}
					}
				});
			}
		});
	}

	private void expandHorizontalTextView() {
		ViewGroup.LayoutParams lp = txtView.getLayoutParams();
		lp.width = TEXTVIEW_VIRTUAL_WIDTH;
		txtView.setLayoutParams(lp);
	}

	private void cutHorizontalTextView() {
		if (txtView.getWidth() != vroot.getMeasuredWidth()) {
			ViewGroup.LayoutParams lp = txtView.getLayoutParams();
			lp.width = vroot.getMeasuredWidth();
			txtView.setLayoutParams(lp);
		}
	}

	private void initVerticalView() {
		txtView.addTextChangedListener(new TextWatcher() {
			@Override
			public void beforeTextChanged(CharSequence charSequence, int i,
					int i2, int i3) {
			}

			@Override
			public void onTextChanged(CharSequence charSequence, int i, int i2,
					int i3) {
			}

			@Override
			public void afterTextChanged(Editable editable) { // Log.d(TAG,"afterTextChanged");
				
				final boolean continueAnimation = mStarted;

				reset();
				prepareAnimation();
				cutVerticalTextView();

				/*
				 * if (continueAnimation) { Log.d(TAG,
				 * "TextChange Restart play"); startMarquee(); }
				 */
				vroot.post(new Runnable() {
					@Override
					public void run() {
						if (continueAnimation) { // Log.d(TAG,
													// "TextChange Restart play");
							startMarquee();
						}
					}
				});
			}
		});
	}

	private void expandVerticalTextView() { // Log.d(TAG,
											// "expandVerticalTextView");
		ViewGroup.LayoutParams lp = txtView.getLayoutParams();
		lp.height = TEXTVIEW_HEIGHT;
		txtView.setLayoutParams(lp);
	}

	private void cutVerticalTextView() {// Log.d(TAG, "cutVerticalTextView");
		if (txtView.getHeight() != vroot.getMeasuredHeight()) {
			ViewGroup.LayoutParams lp = txtView.getLayoutParams();
			lp.height = vroot.getMeasuredHeight();
			txtView.setLayoutParams(lp);
		}
	}

}

水平滚动布局:text_horizontal_scroll.xml

<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="9000dip"
    android:layout_height="match_parent"
    android:scrollbars="none">
    
    	<TextView
       			android:id="@+id/textViewHorizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="left"
                android:textSize="30sp"
                android:textStyle="bold"
                />	
</HorizontalScrollView>

垂直滚动布局:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="9000dip"
    android:scrollbars="none">


        <TextView
            android:id="@+id/textViewVertical"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:textSize="35sp"
            android:textStyle="bold"
            />
 
</ScrollView>

使用:

private TextMarqueeView textContainer;
	void textScorllText(){
		textContainer.setTextContent(content);
		textContainer.setTextSpeed(TextMarqueeView.SPEED_FAST);
		textContainer.setCurrentScrollDirection(TextMarqueeView.SCROLL_HORIZONTAL_LEFT);
		textContainer.startMarquee();
	}




你可能感兴趣的:(android,滚动字幕)