android : marqueeText 实战
程序员文章站
2022-07-07 20:11:28
先看效果,再看实现。好吧,由于视频转 gif ,导致看起来特别的快。实际上比较慢的。不过从视频上面也能看的出来,二者的速度是差不多的。上面一个是 自定义的,最下面的是使用 原生TextView设置跑马灯效果来做的。为什么要自定义这个东西呢,因为 need。 看末尾,滚动一遍之后,要在末尾加上...这样的。说一下实现思路:本身是一个 FrameLayout,里面至少有一个TextView用于显示内容的。如果需要滚动显示,就一共有两个TextView在里面。内容是放在第一个TextView里面的...
先看效果,再看实现。
好吧,由于视频转 gif ,导致看起来特别的快。实际上比较慢的。不过从视频上面也能看的出来,二者的速度是差不多的。
上面一个是 自定义的,最下面的是使用 原生TextView
设置跑马灯效果来做的。
为什么要自定义这个东西呢,因为 need。 看末尾,滚动一遍之后,要在末尾加上...
这样的。
说一下实现思路:
- 本身是一个
FrameLayout
,里面至少有一个TextView
用于显示内容的。如果需要滚动显示,就一共有两个TextView
在里面。 - 内容是放在第一个
TextView
里面的。(如果内容长度不到FrameLayout
的宽度,就不需要滚动。) - 如果内容超出控件宽度了,就滚动第一个
TextView
.滚动一会,让第二个TextView
从右边往左边滚动。等第一个滚动到看不见了,就不管第一个了,继续滚动第二个直到第二个碰到FrameLayout
的左边了。然后就都不滚动了。
说一下预期的实现思路:
预期是直接继承View
去实现,不要弄子Veiw
。 但是,实战没想好,如何实现系统的这种跑马灯思路。
看了TextView
源码,大致猜测是通过canvas
平移实现这种滚动效果的。不过不会弄,就采用了现在的这种方式去做了。
下面是完整源码:
package org.victor.testlivedata.widget;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.apkfuns.logutils.LogUtils;
import java.util.concurrent.atomic.AtomicInteger;
public class MarqueeText extends FrameLayout {
private static final String TRANSLATION_X = "translationX";
private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
private Context mContext;
private TextView mTextView;
private Handler mHandler = new Handler(Looper.getMainLooper());
private volatile AtomicInteger mMeasuredCount = new AtomicInteger(0);
public MarqueeText(@NonNull Context context) {
this(context, null);
}
public MarqueeText(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MarqueeText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
mContext = context;
}
public MarqueeText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mTextView = new TextView(context);
LayoutParams params = new LayoutParams(generateDefaultLayoutParams());
params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
addView(mTextView, params);
}
private int dp2px(float dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
private int sp2px(float sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
}
public void setText(CharSequence text) {
int color = Color.parseColor("#33ff00ff");
// mTextView.setBackgroundColor(color);
mTextView.setSingleLine(true);
mTextView.setText(text);
if (mMeasuredCount.get() > 0) {
innerText(text);
} else {
mHandler.post(new Runnable() {
@Override
public void run() {
if (mMeasuredCount.get() > 0) {
mHandler.removeCallbacks(this);
innerText(text);
return;
}
mHandler.postDelayed(this, 50);
}
});
}
}
private void innerText(CharSequence text) {
float textWidth = getTextWidth(text);
int width = getMeasuredWidth();
LogUtils.e("text: %s,, [%d,%d]", text, (int) textWidth, width);
if (textWidth < width - mTextView.getPaddingLeft() - mTextView.getPaddingRight()) {
// ok
return;
}
TextView other = new TextView(mContext);
LayoutParams params = new LayoutParams(generateDefaultLayoutParams());
params.width = width;
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
other.setText(text);
other.setSingleLine();
other.setEllipsize(TextUtils.TruncateAt.END);
other.setTranslationX(width);
// other.setBackgroundColor(Color.parseColor("#6f0f7f00"));
addView(other, params);
LinearInterpolator interpolator = new LinearInterpolator();
int duration = 1200 * text.length() / 4;
ObjectAnimator animator2 = ObjectAnimator.ofFloat(other, TRANSLATION_X, textWidth, 0);
animator2.setInterpolator(interpolator);
animator2.setDuration(duration);
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mTextView, TRANSLATION_X, 0, -textWidth);
animator1.setInterpolator(interpolator);
animator1.setDuration(duration);
animator1.addUpdateListener(new AnimatorUpdateListener() {
boolean startSecond = false;
boolean did = false;
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue(TRANSLATION_X);
LogUtils.v("value---> [%s,%s]", textWidth, value);
if (!did && -value >= textWidth * 1 / 5) {
startSecond = true;
}
if (did) {
startSecond = false;
}
if (startSecond) {
LogUtils.e("DID=========[%s,%s]", textWidth, value);
animator2.start();
did = true;
}
}
});
animator1.start();
}
private float getTextWidth(CharSequence text) {
TextPaint paint = mTextView.getPaint();
return paint.measureText(text, 0, text.length());
}
public void setText(@StringRes int text) {
setText(mContext.getText(text));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMeasuredCount.set(0);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED) {
return;
}
final TextView child = mTextView;
final int widthPadding;
final int heightPadding;
final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
if (targetSdkVersion >= Build.VERSION_CODES.M) {
widthPadding = getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin;
heightPadding = getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin;
} else {
widthPadding = getPaddingLeft() + getPaddingRight();
heightPadding = getPaddingTop() + getPaddingBottom();
}
int desiredWidth = (int) (getTextWidth(child.getText()) - widthPadding);
if (child.getMeasuredWidth() < desiredWidth) {
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
desiredWidth, MeasureSpec.EXACTLY);
final int childHeightMeasureSpec = getChildMeasureSpec(
heightMeasureSpec, heightPadding, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
LogUtils.e("measure: [%s,%s]", getMeasuredWidth(), getMeasuredHeight());
mMeasuredCount.incrementAndGet();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
LogUtils.e("layout");
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
LogUtils.e("size = [%s,%s] -- [%s,,%s]", w, h, oldw, oldh);
}
}
细节说明:
- 1200 , 因为
android.widget.TextView.Marquee#MARQUEE_DELAY
的值是1200。/4
是试出来的,速度跟原生效果相差无几了。 -
1/5
的前后间距因子也是试出来的。 - 测量的核心代码
int desiredWidth = (int) (getTextWidth(child.getText()) - widthPadding);
这个代码的作用就是让TextView
的宽度无视父控件的宽度,必须完成包含全部的文字。 - 测量的代码,其他部分是参考
android.widget.HorizontalScrollView#onMeasure
来实现的。
本文地址:https://blog.csdn.net/DucklikeJAVA/article/details/107172257
下一篇: qemu-参数解析