当前位置: 移动技术网 > 移动技术>移动开发>Android > android : marqueeText 实战

android : marqueeText 实战

2020年07月08日  | 移动技术网移动技术  | 我要评论

先看效果,再看实现。

在这里插入图片描述

好吧,由于视频转 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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网