欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

android : marqueeText 实战

程序员文章站 2022-04-01 10:49:46
先看效果,再看实现。好吧,由于视频转 gif ,导致看起来特别的快。实际上比较慢的。不过从视频上面也能看的出来,二者的速度是差不多的。上面一个是 自定义的,最下面的是使用 原生TextView设置跑马灯效果来做的。为什么要自定义这个东西呢,因为 need。 看末尾,滚动一遍之后,要在末尾加上...这样的。说一下实现思路:本身是一个 FrameLayout,里面至少有一个TextView用于显示内容的。如果需要滚动显示,就一共有两个TextView在里面。内容是放在第一个TextView里面的...

先看效果,再看实现。

android : marqueeText 实战

好吧,由于视频转 gif ,导致看起来特别的快。实际上比较慢的。不过从视频上面也能看的出来,二者的速度是差不多的。

上面一个是 自定义的,最下面的是使用 原生TextView设置跑马灯效果来做的。

为什么要自定义这个东西呢,因为 need。 看末尾,滚动一遍之后,要在末尾加上...这样的。

说一下实现思路:

  1. 本身是一个 FrameLayout,里面至少有一个TextView用于显示内容的。如果需要滚动显示,就一共有两个TextView在里面。
  2. 内容是放在第一个TextView里面的。(如果内容长度不到FrameLayout的宽度,就不需要滚动。)
  3. 如果内容超出控件宽度了,就滚动第一个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 来实现的。

gist

本文地址:https://blog.csdn.net/DucklikeJAVA/article/details/107172257