一个彷b站醒目留言的控件

 

前段时间b站直播新出了个功能,叫醒目留言。app端的显示效果大概像这个样子:

 

 

看了一下觉得挺有意思,于是自己写了一个控件实现这种效果。话不多说先看效果:

 

演示完效果之后正片开始。核心组件就一个:EyesCatchingMessageView。上代码:

public class EyesCatchingMessageView extends RelativeLayout {

    /**
     * 宽度模式,
     * <p>
     * MODE_FIXED为固定长度模式
     * MODE_WRAP为自适应模式
     */
    public static final int MODE_FIXED = 0x1001;
    public static final int MODE_WRAP = 0x1002;


    private Context context;


    // 背景图层
    private View backgroundView;

    // 计时时变化的图层
    private View timingView;

    // 留言内容图层
    private LinearLayout messageLayout;

    // 头像
    private ImageView portraitImageView;

    // 留言文字
    private TextView messageTextView;


    /**
     * 可设置参数
     */
    // 宽度模式,其值为MODE_FIXED或MODE_WRAP
    private int widthMode;

    // 视图识别id,在创建时由创建者给定
    private int viewId;

    // 控件宽
    private int width;

    // 控件高
    private int height;

    // 头像半径
    private int portraitRadius;

    // 背景图层颜色
    private int backgroundViewColor;

    // 计时图层颜色
    private int timingViewColor;

    // 留言内容
    private String message;

    // 留言字体大小
    private int messageTextSize;

    // 留言字体颜色
    private int messageTextColor;

    // 留言文字左间距
    private int messageLeftMargin;

    // 留言文字右间距
    private int messageRightMargin;

    // 留言显示长度限制
    private int messageLengthLimit;

    // 是否显示留言文字,默认为显示
    private boolean showMessageText;


    // 计时总时长
    private float totalTime;

    // 当前计时时长
    private float currentTime = 0;

    // 是否正在计时状态标识
    private boolean isTiming = false;


    // 剪裁路径,用于把视图显示区域剪裁成需要的形状
    private Path reoundPath;

    // 属性动画,用于计时
    private ValueAnimator timingAnimator;


    // 头像加载接口
    private PortraitLoader portraitLoader;

    // 计时监听接口
    private OnTimingListenerAdapter timingListener;


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

    public EyesCatchingMessageView(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public EyesCatchingMessageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init(context);
    }

    /**
     * 初始化方法,给定可设置参数的默认值
     *
     * @param context
     */
    private void init(Context context) {
        this.context = context;

        widthMode = MODE_WRAP;
        width = dp2px(80);
        height = dp2px(40);
        portraitRadius = dp2px(15);

        backgroundViewColor = Color.parseColor("#70FF6347");
        timingViewColor = Color.parseColor("#FFFF6347");

        messageTextSize = 14;
        messageTextColor = Color.parseColor("#FFFFFF");
        messageLeftMargin = dp2px(10);
        messageRightMargin = dp2px(10);
        messageLengthLimit = 8;
        showMessageText = true;

        setWillNotDraw(false);
    }

    /**
     * 视图创建方法
     */
    private void create() {
        backgroundView = new View(context);
        timingView = new View(context);

        backgroundView.setBackgroundColor(backgroundViewColor);
        timingView.setBackgroundColor(timingViewColor);

        RelativeLayout.LayoutParams backgroundViewParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        RelativeLayout.LayoutParams timingViewParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        backgroundViewParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
        timingViewParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);

        addView(backgroundView, backgroundViewParams);
        addView(timingView, timingViewParams);

        messageLayout = new LinearLayout(context);
        messageLayout.setGravity(Gravity.CENTER_VERTICAL);
        messageLayout.setOrientation(LinearLayout.HORIZONTAL);
        addView(messageLayout, new RelativeLayout.LayoutParams
                (ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));

        portraitImageView = new RoundImageView(context);

        if (portraitLoader != null) {
            portraitLoader.onLoad(viewId, portraitImageView);
        }

        LinearLayout.LayoutParams portraitParams = new LinearLayout.LayoutParams(2 * portraitRadius, 2 * portraitRadius);
        int portraitMargin = height / 2 - portraitRadius;
        portraitParams.setMargins(portraitMargin, portraitMargin, 0, portraitMargin);

        messageLayout.addView(portraitImageView, portraitParams);

        messageTextView = new TextView(context);

        messageTextView.setText(cutMessage(message));
        messageTextView.setTextColor(messageTextColor);
        messageTextView.setTextSize(messageTextSize);
        messageTextView.setMaxLines(1);
        messageTextView.setVisibility(showMessageText ? VISIBLE : INVISIBLE);

        LinearLayout.LayoutParams messageParams = new LinearLayout.LayoutParams
                (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        messageParams.setMargins(messageLeftMargin, 0, messageRightMargin, 0);

        messageLayout.addView(messageTextView, messageParams);

        messageLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                messageLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);

                if (widthMode == MODE_WRAP) {
                    width = messageLayout.getWidth();
                }

                getLayoutParams().width = width;
                getLayoutParams().height = height;

                // 创建一个圆角方形的剪裁区域
                reoundPath = new Path();
                reoundPath.addRoundRect(
                        new RectF(0, 0, width, height),
                        new float[]{
                                width / 2f, width / 2f,
                                width / 2f, width / 2f,
                                width / 2f, width / 2f,
                                width / 2f, width / 2f},
                        Path.Direction.CW);
            }
        });
    }

    @Override
    protected void onDraw(Canvas canvas) {

        if (reoundPath != null) {

            // 剪裁视图显示区域
            canvas.clipPath(reoundPath);
        }

        super.onDraw(canvas);
    }

    /**
     * 开始计时方法
     *
     * @param timingTotalTime 计时总时长,单位秒
     */
    public void startTiming(float timingTotalTime) {
        startTiming(timingTotalTime, timingTotalTime);
    }

    /**
     * 开始计时方法
     *
     * @param timingTotalTime  计时总时长,单位秒
     * @param initialTime timingTotalTime=100s,initialTime=60s,则表示从剩余进度60%处开始计时
     */
    public void startTiming(float timingTotalTime, float initialTime) {
        if (isTiming) {
            return;
        }

        if (timingTotalTime <= 0 || initialTime < 0 || initialTime > timingTotalTime) {
            return;
        }

        this.totalTime = timingTotalTime;

        timingAnimator = ValueAnimator.ofFloat(new float[]{initialTime * 1000, 0});
        timingAnimator.setInterpolator(new LinearInterpolator());
        timingAnimator.setDuration((long) (initialTime * 1000));
        timingAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationCancel(Animator animation) {
                currentTime = 0;
                isTiming = false;

                if (timingListener != null) {
                    timingListener.onCancel(viewId);
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                currentTime = 0;
                isTiming = false;

                if (timingListener != null) {
                    timingListener.onFinish(viewId);
                }
            }

            @Override
            public void onAnimationStart(Animator animation) {
                currentTime = totalTime;
                isTiming = true;

                if (timingListener != null) {
                    timingListener.onStart(viewId);
                }
            }
        });
        timingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentTime = (float) animation.getAnimatedValue() / 1000;
                refreshTimingView(currentTime / totalTime);

                if (timingListener != null) {
                    timingListener.onTiming(viewId, totalTime, currentTime, currentTime / totalTime);
                }
            }
        });

        timingAnimator.start();
    }

    /**
     * 取消计时方法
     */
    public void cancelTiming() {
        if (!isTiming) {
            return;
        }

        if (timingAnimator != null && timingAnimator.isRunning()) {
            timingAnimator.cancel();
        }
    }

    /**
     * 更新计时图层
     *
     * @param percentage 剩余进度百分比
     */
    private void refreshTimingView(float percentage) {
        timingView.getLayoutParams().width = (int) (width * percentage);
        timingView.requestLayout();
    }

    /**
     * 限制留言显示
     *
     * @param message 留言内容
     * @return
     */
    private String cutMessage(String message) {

        if (message == null) {
            return null;
        }

        if (message.length() <= messageLengthLimit) {
            return message;
        } else {
            return message.substring(0, messageLengthLimit) + "...";
        }
    }

    /**
     * 设置宽度模式
     *
     * @param widthMode 宽度模式,可选项:
     *                  MODE_FIXED 固定长度模式
     *                  MODE_WRAP  自适应模式
     */
    public void setWidthMode(int widthMode) {

        if (!(widthMode == MODE_FIXED || widthMode == MODE_FIXED)) {
            return;
        }

        this.widthMode = widthMode;
    }

    /**
     * 设置视图识别id
     *
     * @param viewId 识别id
     */
    public void setViewId(int viewId) {
        this.viewId = viewId;
    }

    /**
     * 获取视图识别id
     *
     * @return
     */
    public int getViewId() {
        return viewId;
    }

    /**
     * 设置视图宽度,仅在宽度模式(widthMode)为固定长度模式(MODE_FIXED)时有效
     *
     * @param width 宽度,单位dp
     */
    public void setWidth(int width) {
        this.width = dp2px(width);
    }

    /**
     * 设置视图高度
     *
     * @param height 高度,单位dp
     */
    public void setHeight(int height) {
        this.height = dp2px(height);
    }

    /**
     * 设置头像半径
     *
     * @param portraitRadius 半径,单位dp
     */
    public void setPortraitRadius(int portraitRadius) {
        this.portraitRadius = dp2px(portraitRadius);
    }

    /**
     * 设置背景图层颜色
     *
     * @param backgroundViewColor 颜色值
     */
    public void setBackgroundViewColor(int backgroundViewColor) {
        this.backgroundViewColor = backgroundViewColor;

        if (backgroundView != null) {
            backgroundView.setBackgroundColor(backgroundViewColor);
        }
    }

    /**
     * 设置计时图层颜色
     *
     * @param timing
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值