android Custom View to Achieve Horse Lantern Effect

  • 2021-09-05 00:51:12
  • OfStack

TextView, which comes with android, can achieve the effect of horse racing, but it has many limitations. For example, it is necessary to set ellipsize= "marquee", get focusable= "true" and set singleLine= "true". The contents in the control need to exceed the length of the control itself, and the scrolling speed and scrolling pause and continue scrolling functions cannot be controlled. Various restrictions make it particularly difficult to use, and it is almost impossible to use it in the production environment. In this context, it is necessary to customize View to achieve the ticker effect.

Uses the main method: Custom View overwrites onDraw method, through canvas. drawText () method to display the text, uses Handler unceasingly draws the text, and controls the text to start drawing the X axis position, realizes the continuous scrolling effect.

Implementation steps:

1. Customize class of view:


import android.annotation.SuppressLint;
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.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

public class MarqueenView extends View {

 /*
  * 1 , custom View Properties of  2 , in View Get our custom properties from the constructor of  [ 3 , rewrite onMesure ] 4 , rewrite onDraw
  */

 /*
  *  Text 
  */
 private String mTitleText;

 /**
  *  Color of text 
  */
 private int mTitleTextColor;

 /**
  *  Size of text 
  */
 private int mTitleTextSize;

 /**
  *  Control the scope of text drawing when drawing 
  */
 private Rect mBound, usualBound;

 //  Paintbrush 
 private Paint mPaint;

 private int spead = 15;

 private int length = 3;

 private int currentLength;

 @SuppressLint("HandlerLeak")
 private Handler handler = new Handler() {
  @Override
  public void handleMessage(android.os.Message msg) {
   if (mBound.width() <= getWidth()) {
    if (currentLength >= 120 + (getWidth() / length) * length) {
     currentLength = length;
    } else {
     currentLength = currentLength + length;
    }
   } else {
    if (currentLength >= (mBound.width() + 120)) {
     currentLength = length;
    } else {
     currentLength = currentLength + length;
    }
   }

   invalidate();
   handler.sendEmptyMessageDelayed(0, spead);

  };
 };

 public MarqueenView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
  currentLength = length;
 }

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

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

  TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);

  int n = a.getIndexCount();
  for (int i = 0; i < n; i++) {
   int attr = a.getIndex(i);

   switch (attr) {
   case R.styleable.CustomTitleView_titleText:
    mTitleText = a.getString(attr);
    break;
   case R.styleable.CustomTitleView_titleTextColor:
    mTitleTextColor = a.getColor(attr, Color.BLACK);
    break;
   case R.styleable.CustomTitleView_titleTextSize:
    mTitleTextSize = a.getDimensionPixelSize(attr,
      (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
    break;
   }

  }
  a.recycle();

  mPaint = new Paint();
  mPaint.setTextSize(mTitleTextSize);

  mBound = new Rect();
  usualBound = new Rect();
  mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
  mPaint.getTextBounds("1234567890QqYy How do you do ", 0, 16, usualBound);
 }


 /*
  * EXACTLY : 1 Either an explicit value is set or MATCH_PARENT AT_MOST Indicates that sub-layouts are restricted to 1 Within the maximum value of, 1 Be general WARP_CONTENT
  * UNSPECIFIED Indicates that the child layout can be as large as it wants, and is rarely used 
  */

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

  int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  int widthSize = MeasureSpec.getSize(widthMeasureSpec);

  int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  int width;
  int height;
  if (widthMode == MeasureSpec.EXACTLY) {
   width = widthSize;
  } else {
   mPaint.setTextSize(mTitleTextSize);
   mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
   float textWidth = mBound.width();//  Font width 

   //  Control padding
   int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
   width = desired;
  }

  if (heightMode == MeasureSpec.EXACTLY) {
   height = heightSize;
  } else {
   mPaint.setTextSize(mTitleTextSize);
   mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
   float textHeight = mBound.height();
   int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
   height = desired;
  }
  // setMeasuredDimension((width / length) * length, height);
  setMeasuredDimension(width, height + usualBound.bottom);
 }

 @Override
 protected void onDraw(Canvas canvas) {


  mPaint.setColor(mTitleTextColor);
  if (mBound.width() <= getWidth()) {


   if (currentLength < mBound.width()) {
    canvas.drawText(mTitleText, getPaddingLeft() - currentLength, getHeight() - usualBound.bottom, mPaint);
   }

   if (currentLength >= 120) {
    canvas.drawText(mTitleText, getWidth() - currentLength + 120, getHeight() - usualBound.bottom, mPaint);
   }

  } else {


   if (currentLength < mBound.width()) {
    canvas.drawText(mTitleText, getPaddingLeft() - currentLength, getHeight() - usualBound.bottom, mPaint);
   }

   if (currentLength >= mBound.width() - getWidth() + 120) {
    canvas.drawText(mTitleText, 120 - currentLength + mBound.width(), getHeight() - usualBound.bottom, mPaint);
   }

  }

 }

 public static int getFontHeight(Integer textSize) {
  Paint paint = new Paint();
  if (textSize != null) {
   paint.setTextSize(textSize);
  }
  FontMetrics fm = paint.getFontMetrics();
  return (int) (fm.descent - fm.ascent);
 }

 public void startScroll() {
  handler.removeMessages(0);
  handler.sendEmptyMessage(0);
 }

 public void stopScroll() {
  handler.removeMessages(0);
  currentLength = 0;
  invalidate();
 }

 public void setScrollLength(int length) {
  this.length = length;
  currentLength = length;
 }

 public void setSpead(int spead) {
  this.spead = spead;
 }

 public void setText(String msg) {
  mTitleText = msg;
  mBound = new Rect();
  mPaint.getTextBounds(msg, 0, mTitleText.length(), mBound);
 }


}

2. Custom attributes, font size, color, and initial content need to be added to attrs. xml file of values, which needs to be improved here


<?xml version="1.0" encoding="utf-8"?>
<resources>

 <attr name="titleText" format="string" />
 <attr name="titleTextColor" format="color" />
 <attr name="titleTextSize" format="dimension" />

 <declare-styleable name="CustomTitleView">
  <attr name="titleText" />
  <attr name="titleTextColor" />
  <attr name="titleTextSize" />
 </declare-styleable>
</resources>

3. Reference this control in the layout file of layout


<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:custom="http://schemas.android.com/apk/res/*** Write your bag name here ***"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical" >

 <com.example.MarqueenView
 android:id="@+id/main_marquee_view"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 custom:titleText=" "
 custom:titleTextColor="#666666"
 custom:titleTextSize="14sp"/>
 </LinearLayout>

4. After the above is ready, you can set the ticker in activity and start scrolling


mv = (MarqueenView) findViewById(R.id.main_marquee_view);
mv.stopScroll();
mv.setText(" Scroll content ");//  Set the display content 
//mv.setSpead(15);// Scroll frequency, default 15 Milliseconds 1 Second, if the value is too small, it will affect the efficiency 
//mv.setScrollLength(3);// Default each left shift 3px If the value is too large, there will be a sense of pause, and if the value is too small, scrolling will slow down 
mv.startScroll();

// Remember in Ondestroy Stop scrolling in the middle to facilitate recycling 
@Override
 protected void onDestroy() {
  try {
   mv.stopScroll();
   mv = null;
  } catch (Throwable e) {
   e.printStackTrace();
  }
  super.onDestroy();
 }

Related articles: