一个彷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 timingViewColor 颜色值
     */
    public void setTimingViewColor(int timingViewColor) {
        this.timingViewColor = timingViewColor;

        if (timingView != null) {
            timingView.setBackgroundColor(timingViewColor);
        }
    }


    /**
     * 设置留言内容
     *
     * @param message 留言内容
     */
    public void setMessage(String message) {
        this.message = message;

        if (messageTextView != null) {
            messageTextView.setText(cutMessage(message));
        }
    }

    /**
     * 设置留言字体大小
     *
     * @param messageTextSize 字体大小,单位sp
     */
    public void setMessageTextSize(int messageTextSize) {
        this.messageTextSize = messageTextSize;

        if (messageTextView != null) {
            messageTextView.setTextSize(messageTextSize);
        }
    }

    /**
     * 设置留言字体颜色
     *
     * @param messageTextColor 颜色值
     */
    public void setMessageTextColor(int messageTextColor) {
        this.messageTextColor = messageTextColor;

        if (messageTextView != null) {
            messageTextView.setTextColor(messageTextColor);
        }
    }

    /**
     * 设置留言文字左间距
     *
     * @param messageLeftMargin 间距,单位dp
     */
    public void setMessageLeftMargin(int messageLeftMargin) {
        this.messageLeftMargin = dp2px(messageLeftMargin);
    }

    /**
     * 设置留言文字右间距
     *
     * @param messageRightMargin 间距,单位dp
     */
    public void setMessageRightMargin(int messageRightMargin) {
        this.messageRightMargin = dp2px(messageRightMargin);
    }

    /**
     * 设置留言显示长度限制
     *
     * @param messageLengthLimit 长度限制
     */
    public void setMessageLengthLimit(int messageLengthLimit) {
        this.messageLengthLimit = messageLengthLimit;

        if (messageTextView != null) {
            messageTextView.setText(cutMessage(message));
        }
    }

    /**
     * 设置是否显示留言文字
     *
     * @param showMessageText
     */
    public void setShowMessageText(boolean showMessageText) {
        this.showMessageText = showMessageText;
    }

    /**
     * 获取剩余进度百分比
     *
     * @return
     */
    public float getPercentage() {

        if (isTiming && totalTime > 0) {
            return currentTime / totalTime;
        }

        return 0;
    }

    /**
     * 设置计时监听接口
     *
     * @param timingListener
     */
    public void setTimingListener(OnTimingListenerAdapter timingListener) {
        this.timingListener = timingListener;
    }

    /**
     * 设置头像加载接口
     *
     * @param portraitLoader
     */
    public void setPortraitLoader(PortraitLoader portraitLoader) {
        this.portraitLoader = portraitLoader;
    }

    /**
     * 头像加载接口
     */
    public interface PortraitLoader {
        void onLoad(int viewId, ImageView portraitImageView);
    }

    /**
     * 计时监听接口
     */
    private interface OnTimingListener {

        /**
         * 计时开始时回调
         *
         * @param viewId
         */
        void onStart(int viewId);

        /**
         * 计时过程中回调
         *
         * @param viewId
         * @param totalTime   计时总时长
         * @param currentTime 当前计时时长
         * @param percentage  剩余进度百分比
         */
        void onTiming(int viewId, float totalTime, float currentTime, float percentage);

        /**
         * 计时结束时回调
         *
         * @param viewId
         */
        void onFinish(int viewId);

        /**
         * 计时取消是回调
         *
         * @param viewId
         */
        void onCancel(int viewId);
    }

    /**
     * 计时监听接口适配器,作用是让调用者自助选择需要监听的回调方法
     */
    public static class OnTimingListenerAdapter implements OnTimingListener {

        @Override
        public void onStart(int viewId) {

        }

        @Override
        public void onTiming(int viewId, float totalTime, float currentTime, float percentage) {

        }

        @Override
        public void onFinish(int viewId) {

        }

        @Override
        public void onCancel(int viewId) {

        }
    }

    private int dp2px(int dpVal) {

        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());
    }

    /**
     * 视图建造者
     * <p>
     * 可以通过视图建造者方便的创建一个视图对象
     */
    public static class EyesCatchingMessageViewBuilder {

        private EyesCatchingMessageView eyesCatchingMessageView;

        public EyesCatchingMessageViewBuilder(Context context) {
            eyesCatchingMessageView = new EyesCatchingMessageView(context);
        }

        public EyesCatchingMessageViewBuilder setWidthMode(int widthMode) {
            eyesCatchingMessageView.setWidthMode(widthMode);

            return this;
        }

        public EyesCatchingMessageViewBuilder setViewId(int viewId) {
            eyesCatchingMessageView.setViewId(viewId);

            return this;
        }

        public EyesCatchingMessageViewBuilder setWidth(int width) {
            eyesCatchingMessageView.setWidth(width);

            return this;
        }

        public EyesCatchingMessageViewBuilder setHeight(int height) {
            eyesCatchingMessageView.setHeight(height);

            return this;
        }

        public EyesCatchingMessageViewBuilder setPortraitRadius(int portraitRadius) {
            eyesCatchingMessageView.setPortraitRadius(portraitRadius);

            return this;
        }

        public EyesCatchingMessageViewBuilder setBackgroundViewColor(int backgroundViewColor) {
            eyesCatchingMessageView.setBackgroundViewColor(backgroundViewColor);

            return this;
        }

        public EyesCatchingMessageViewBuilder setTimingViewColor(int timingViewColor) {
            eyesCatchingMessageView.setTimingViewColor(timingViewColor);

            return this;
        }

        public EyesCatchingMessageViewBuilder setMessage(String message) {
            eyesCatchingMessageView.setMessage(message);

            return this;
        }

        public EyesCatchingMessageViewBuilder setMessageTextSize(int messageTextSize) {
            eyesCatchingMessageView.setMessageTextSize(messageTextSize);

            return this;
        }

        public EyesCatchingMessageViewBuilder setMessageTextColor(int messageTextColor) {
            eyesCatchingMessageView.setMessageTextColor(messageTextColor);

            return this;
        }

        public EyesCatchingMessageViewBuilder setMessageLeftMargin(int messageLeftMargin) {
            eyesCatchingMessageView.setMessageLeftMargin(messageLeftMargin);

            return this;
        }

        public EyesCatchingMessageViewBuilder setMessageRightMargin(int messageRightMargin) {
            eyesCatchingMessageView.setMessageRightMargin(messageRightMargin);

            return this;
        }

        public EyesCatchingMessageViewBuilder setMessageLengthLimit(int messageLengthLimit) {
            eyesCatchingMessageView.setMessageLengthLimit(messageLengthLimit);

            return this;
        }

        public EyesCatchingMessageViewBuilder setShowMessageText(boolean showMessageText) {
            eyesCatchingMessageView.setShowMessageText(showMessageText);

            return this;
        }

        public EyesCatchingMessageViewBuilder setTimingListener(OnTimingListenerAdapter timingListener) {
            eyesCatchingMessageView.setTimingListener(timingListener);

            return this;
        }

        public EyesCatchingMessageViewBuilder setPortraitLoader(PortraitLoader portraitLoader) {
            eyesCatchingMessageView.setPortraitLoader(portraitLoader);

            return this;
        }

        /**
         * 创建醒目留言视图对象
         *
         * @return  视图对象实例
         */
        public EyesCatchingMessageView create() {
            eyesCatchingMessageView.create();

            return eyesCatchingMessageView;
        }
    }
}

简单讲一下实现原理。原理其实并不复杂,只需要知道明白两点,一是圆角矩形的图案是怎么实现的,通过下面这句话实现:

canvas.clipPath(reoundPath);

即在绘制图形时手动把画布剪裁成了圆角矩形。

二是计时动画如何实现。通过属性动画,也就是这句:

timingAnimator = ValueAnimator.ofFloat(new float[]{initialTime * 1000, 0});

然后在属性动画开启后,不断更新代表进度的View的宽度来实现计时效果,也就是这两句话:

timingView.getLayoutParams().width = (int) (width * percentage);
timingView.requestLayout();

这个控件里需要用到另一个圆形图片控件RoundImageView,这个在我之前的博客里介绍过,需要的戳这里查看,这里就不赘述了。

然后就可以使用了。控件提供了一个内部建造者类EyesCatchingMessageViewBuilder来让调用者快捷地创建一个EyesCatchingMessageView对象。当然你也可以new一个EyesCatchingMessageView对象,然后调用它自身的各种set方法设置参数来完成创建,我也不拦着你。好了简单写几句代码:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    EyesCatchingMessageView eyesCatchingMessageView = new EyesCatchingMessageView.EyesCatchingMessageViewBuilder(this)
            .setViewId(0)
            .setPortraitLoader(new EyesCatchingMessageView.PortraitLoader() {
                @Override
                public void onLoad(int viewId, ImageView portraitImageView) {
                    portraitImageView.setImageResource(R.drawable.huaji);
                }
            })
            .setMessage("哈哈哈")
            .create();

    addContentView(eyesCatchingMessageView,
            new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));

    eyesCatchingMessageView.startTiming(10);
}

运行一下看一下效果:

用法很简单,就是new一个EyesCatchingMessageViewBuilder对象,然后set各种参数,最后调用它的create()方法就可以创建一个EyesCatchingMessageView对象。最后调用EyesCatchingMessageView的startTiming方法来开启计时。

setViewId方法是给控件添加一个识别id,由调用者给定,只要保证它的唯一性让你能认出它来就可以了。

setPortraitLoader是设置头像加载器,因为具体项目中头像加载往往是异步的,具体加载方法也不一样,所以通过这个接口把加载过程抽象出来让调用者自己实现。

当然EyesCatchingMessageView还支持各种别的参数设置,你可以设置宽高、颜色、头像半径,文字颜色大小间距等等,还可以通过接口监听计时过程。

修改一下代码:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    EyesCatchingMessageView eyesCatchingMessageView = new EyesCatchingMessageView.EyesCatchingMessageViewBuilder(this)
            .setWidthMode(EyesCatchingMessageView.MODE_WRAP)
            .setViewId(0)
            .setHeight(80)
            .setPortraitRadius(30)
            .setMessage("哈哈哈哈哈哈哈哈哈哈哈哈")
            .setMessageLengthLimit(7)
            .setBackgroundViewColor(Color.parseColor("#70FFD700"))
            .setTimingViewColor(Color.parseColor("#FFD700"))
            .setMessageTextColor(Color.parseColor("#000000"))
            .setMessageTextSize(30)
            .setMessageLeftMargin(20)
            .setMessageRightMargin(20)
            .setPortraitLoader(new EyesCatchingMessageView.PortraitLoader() {
                @Override
                public void onLoad(int viewId, ImageView portraitImageView) {
                    portraitImageView.setImageResource(R.drawable.huaji);
                }
            })
            .setTimingListener(new EyesCatchingMessageView.OnTimingListenerAdapter() {
                @Override
                public void onFinish(int viewId) {
                    Toast.makeText(MainActivity.this, "Timing Finish", Toast.LENGTH_SHORT).show();
                }
            })
            .create();

    addContentView(eyesCatchingMessageView,
            new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));

    eyesCatchingMessageView.startTiming(10);
}

各种参数的意义注释里写得很清楚了,大家自己看注释就好。这里就特别提一嘴setWidthMode这个方法,这个方法是设置醒目留言的宽度显示模式。可选项有两种:MODE_FIXED和MODE_WRAP。MODE_FIXED是固定长度模式,该模式下可通过setWidth方法给定醒目留言的宽度;MODE_WRAP是自适应模式,无需给定宽度,其宽度会根据头像宽度及文字长度自适应。

好了,运行一下看一下效果:
 

到此为止,基本效果是实现了,但实际项目中使用起来肯定是不好用的,因为它功能集成地不够彻底,具体增加、删除、管理醒目留言的逻辑代码需要自己写。怎么办嘞?再写一个布局控件统一管理醒目留言对象。上代码:

public class EyesCatchingMessageLayout extends LinearLayout {

    private Context context;

    // 横向滚动容器
    private HorizontalScrollView scrollView;

    // 醒目留言视图容器
    private LinearLayout parentLayout;

    // 醒目留言视图对象集合
    private ArrayList<EyesCatchingMessageView> messageViews;

    // 醒目留言数据集合
    private ArrayList<MessageItem> messageItems;

    // 醒目留言配置集合
    private ArrayList<EyesCatchingMessageConfig> customConfigs;


    /**
     * 可设置参数
     */
    // 醒目留言向上间距
    private int paddingTop;

    // 醒目留言向下间距
    private int paddingBottom;

    // 第一个醒目留言向左间距
    private int paddingLeft;

    // 最后一个醒目留言向右间距
    private int paddingRight;

    // 醒目留言向下间距
    private int messageMargin;

    // 醒目留言参数配置类
    private EyesCatchingMessageConfig config;

    // 排序比较器
    private SortComparator sortComparator;

    // 醒目留言点击监听接口
    private OnMessageClickListener clickListener;

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

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


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

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

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

        init(context);
    }

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

        paddingTop = dp2px(10);
        paddingBottom = dp2px(10);
        paddingLeft = dp2px(15);
        paddingRight = dp2px(15);
        messageMargin = dp2px(10);

        setOrientation(HORIZONTAL);
        setGravity(Gravity.CENTER_VERTICAL);

        messageViews = new ArrayList<>();
        messageItems = new ArrayList<>();
        customConfigs = new ArrayList<>();

        sortComparator = new SortComparator() {
            @Override
            public boolean onCompare(MessageItem item1, MessageItem item2) {
                return true;
            }
        };

        create();
    }

    /**
     * 创建方法,创建视图
     */
    private void create() {
        scrollView = new HorizontalScrollView(context);
        parentLayout = new LinearLayout(context);

        parentLayout.setOrientation(HORIZONTAL);
        parentLayout.setGravity(Gravity.CENTER_VERTICAL);

        scrollView.setHorizontalScrollBarEnabled(false);
        scrollView.addView(parentLayout, new HorizontalScrollView.LayoutParams
                (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

        LinearLayout.LayoutParams scrollViewParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1);

        addView(scrollView, scrollViewParams);
    }

    /**
     * 添加一个醒目留言
     *
     * @param messageItem 数据对象
     */
    public void addMessageView(MessageItem messageItem) {
        doAddMessageView(messageItem, null, null, null);
    }

    /**
     * 添加一个醒目留言
     *
     * @param messageItem 数据对象
     * @param width       指定醒目留言宽度,单位dp
     */
    public void addMessageView(MessageItem messageItem, int width) {
        doAddMessageView(messageItem, width, null, null);
    }

    /**
     * 添加一个醒目留言
     *
     * @param messageItem         数据对象
     * @param backgroundViewColor 指定背景图层颜色
     * @param timingViewColor     指定计时图层颜色
     */
    public void addMessageView(MessageItem messageItem, int backgroundViewColor, int timingViewColor) {
        doAddMessageView(messageItem, null, backgroundViewColor, timingViewColor);
    }

    /**
     * 添加一个醒目留言
     *
     * @param messageItem         数据对象
     * @param width               指定醒目留言宽度,单位dp
     * @param backgroundViewColor 指定背景图层颜色
     * @param timingViewColor     指定计时图层颜色
     */
    public void addMessageView(MessageItem messageItem, int width, int backgroundViewColor, int timingViewColor) {
        doAddMessageView(messageItem, width, backgroundViewColor, timingViewColor);
    }

    /**
     * 添加多个醒目留言
     *
     * @param messageList 数据对象集合
     */
    public void addMessageViews(List<MessageItem> messageList) {

        if (messageList == null) {
            return;
        }

        for (int i = 0; i < messageList.size(); i++) {
            addMessageView(messageList.get(i));
        }
    }

    /**
     * 执行添加醒目留言方法
     *
     * @param messageItem
     * @param width
     * @param backgroundViewColor
     * @param timingViewColor
     */
    private void doAddMessageView(MessageItem messageItem, Integer width, Integer backgroundViewColor, Integer timingViewColor) {

        if (messageItem == null) {
            return;
        }

        EyesCatchingMessageConfig customConfig = new EyesCatchingMessageConfig.EyesCatchingMessageConfigBuilder()
                .setWidth(width)
                .setBackgroundViewColor(backgroundViewColor)
                .setTimingViewColor(timingViewColor)
                .create();

        ArrayList<MessageItem> tempItems = new ArrayList<>();
        ArrayList<EyesCatchingMessageConfig> tempConfigs = new ArrayList<>();
        tempItems.add(messageItem);
        tempConfigs.add(customConfig);
        tempItems.addAll(messageItems);
        tempConfigs.addAll(customConfigs);

        for (int i = tempItems.size() - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (!sortComparator.onCompare(tempItems.get(j), tempItems.get(j + 1))) {
                    MessageItem tempItem = tempItems.get(j + 1);
                    tempItems.set(j + 1, tempItems.get(j));
                    tempItems.set(j, tempItem);

                    EyesCatchingMessageConfig tempConfig = tempConfigs.get(j + 1);
                    tempConfigs.set(j + 1, tempConfigs.get(j));
                    tempConfigs.set(j, tempConfig);
                }
            }
        }

        cancelAllTiming();

        messageItems.clear();
        customConfigs.clear();
        messageItems.addAll(tempItems);
        customConfigs.addAll(tempConfigs);

        for (int i = 0; i < messageItems.size(); i++) {
            addEyesCatchingMessageView(messageItems.get(i), customConfigs.get(i));
        }
    }

    /**
     * 添加醒目留言方法
     *
     * @param messageItem  数据对象
     * @param customConfig 自定义配置对象
     */
    private void addEyesCatchingMessageView(MessageItem messageItem, EyesCatchingMessageConfig customConfig) {

        EyesCatchingMessageView.EyesCatchingMessageViewBuilder builder = new EyesCatchingMessageView.EyesCatchingMessageViewBuilder(context);

        writeMessageConfig(builder, config);
        writeMessageConfig(builder, customConfig);

        EyesCatchingMessageView view = builder
                .setViewId(messageItem.getViewId())
                .setMessage(messageItem.getMessage())
                .setTimingListener(new EyesCatchingMessageView.OnTimingListenerAdapter() {
                    @Override
                    public void onStart(int viewId) {
                        if (timingListener != null) {
                            int position = findPostionByViewId(viewId);
                            if (position != -1) {
                                timingListener.onStart(position, messageItems.get(position));
                            }
                        }
                    }

                    @Override
                    public void onTiming(int viewId, float totalTime, float currentTime, float percentage) {
                        int position = findPostionByViewId(viewId);
                        if (position != -1) {

                            messageItems.get(position).setInitialTime(currentTime);

                            if (timingListener != null) {
                                timingListener.onTiming(position, messageItems.get(position), currentTime, percentage);
                            }
                        }
                    }

                    @Override
                    public void onFinish(int viewId) {
                        if (timingListener != null) {
                            int position = findPostionByViewId(viewId);
                            if (position != -1) {
                                timingListener.onFinish(position, messageItems.get(position));
                            }
                        }

                        removeEyesCatchingMessageView(viewId);
                    }

                    @Override
                    public void onCancel(int viewId) {
                        if (timingListener != null) {
                            int position = findPostionByViewId(viewId);
                            if (position != -1) {
                                timingListener.onCancel(position, messageItems.get(position));
                            }
                        }
                    }
                })
                .setPortraitLoader(new EyesCatchingMessageView.PortraitLoader() {
                    @Override
                    public void onLoad(int viewId, ImageView portraitImageView) {
                        if (portraitLoader != null) {
                            int position = findPostionByViewId(viewId);
                            portraitLoader.onLoad(position, messageItems.get(position), portraitImageView);
                        }
                    }
                })
                .create();

        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.setMargins(messageViews.size() == 0 ? paddingLeft : messageMargin, paddingTop, paddingRight, paddingBottom);

        if (messageViews.size() != 0) {
            ((LinearLayout.LayoutParams) messageViews.get(messageViews.size() - 1).getLayoutParams())
                    .setMargins(messageViews.size() == 1 ? paddingLeft : messageMargin, paddingTop, 0, paddingBottom);
        }

        view.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (clickListener != null) {
                    EyesCatchingMessageView currentView = (EyesCatchingMessageView) v;
                    int position = findPostionByViewId(currentView.getViewId());
                    clickListener.onMessageClick(position, currentView.getPercentage(), messageItems.get(position));
                }
            }
        });

        messageViews.add(view);

        parentLayout.addView(view, params);

        view.startTiming(messageItem.getTotalTime(), messageItem.getInitialTime());
    }

    /**
     * 手动取消一个醒目留言
     *
     * @param viewId 识别id
     */
    public void cancelTimingByViewId(int viewId) {

        int position = findPostionByViewId(viewId);

        if (position != -1) {
            messageViews.get(position).cancelTiming();
        }

    }

    /**
     * 手动取消所有醒目留言
     */
    public void cancelAllTiming() {

        OnTimingListenerAdapter temp = timingListener;
        timingListener = null;

        while (messageViews.size() > 0) {
            messageViews.get(0).cancelTiming();
        }

        timingListener = temp;
    }

    /**
     * 获取醒目留言条数
     *
     * @return
     */
    public int getMessageViewCount() {
        return messageViews.size();
    }

    /**
     * 移除指定醒目留言
     *
     * @param viewId 识别id
     */
    private void removeEyesCatchingMessageView(int viewId) {

        int position = findPostionByViewId(viewId);

        if (position != -1) {
            parentLayout.removeView(messageViews.get(position));
            messageViews.remove(position);
            messageItems.remove(position);
            customConfigs.remove(position);
        }

    }

    /**
     * 写入配置对象中的数据
     *
     * @param builder
     * @param config
     */
    private void writeMessageConfig(EyesCatchingMessageView.EyesCatchingMessageViewBuilder builder, EyesCatchingMessageConfig config) {

        if (config == null) {
            return;
        }

        if (config.widthMode != null) {
            builder.setWidthMode(config.widthMode);
        }
        if (config.width != null) {
            builder.setWidth(config.width);
        }
        if (config.height != null) {
            builder.setHeight(config.height);
        }
        if (config.portraitRadius != null) {
            builder.setPortraitRadius(config.portraitRadius);
        }
        if (config.backgroundViewColor != null) {
            builder.setBackgroundViewColor(config.backgroundViewColor);
        }
        if (config.timingViewColor != null) {
            builder.setTimingViewColor(config.timingViewColor);
        }
        if (config.messageTextSize != null) {
            builder.setMessageTextSize(config.messageTextSize);
        }
        if (config.messageTextColor != null) {
            builder.setMessageTextColor(config.messageTextColor);
        }
        if (config.messageLengthLimit != null) {
            builder.setMessageLengthLimit(config.messageLengthLimit);
        }
        if (config.messageLeftMargin != null) {
            builder.setMessageLeftMargin(config.messageLeftMargin);
        }
        if (config.messageRightMargin != null) {
            builder.setMessageRightMargin(config.messageRightMargin);
        }
        if (config.showMessageText != null) {
            builder.setShowMessageText(config.showMessageText);
        }
    }

    /**
     * 根据viewId查找醒目留言位置
     *
     * @param viewId
     * @return
     */
    private int findPostionByViewId(int viewId) {

        for (int i = 0; i < messageItems.size(); i++) {
            if (messageItems.get(i).getViewId() == viewId) {
                return i;
            }
        }

        return -1;
    }

    /**
     * 这只配置对象
     *
     * @param config
     */
    public void setConfig(EyesCatchingMessageConfig config) {
        this.config = config;
    }

    /**
     * 设置醒目留言上下左右间距,单位dp
     *
     * @param paddingLeft   第一个醒目留言向左间距
     * @param paddingTop    醒目留言向上间距
     * @param paddingRight  最后一个醒目留言向右间距
     * @param paddingBottom 醒目留言向下间距
     */
    public void setMessagePadding(int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
        this.paddingLeft = dp2px(paddingLeft);
        this.paddingTop = dp2px(paddingTop);
        this.paddingRight = dp2px(paddingRight);
        this.paddingBottom = dp2px(paddingBottom);
    }

    /**
     * 设置醒目留言间距
     *
     * @param messageMargin 间距,单位dp
     */
    public void setMessageMargin(int messageMargin) {
        this.messageMargin = dp2px(messageMargin);
    }

    /**
     * 设置排序比较器
     *
     * @param sortComparator
     */
    public void setSortComparator(SortComparator sortComparator) {
        if (sortComparator == null) {
            return;
        }

        this.sortComparator = sortComparator;
    }

    /**
     * 设置点击监听接口
     *
     * @param clickListener
     */
    public void setClickListener(OnMessageClickListener clickListener) {
        this.clickListener = clickListener;
    }

    /**
     * 设置头像加载接口
     * <p>
     * 注:此方法需在调用addMessageView方法添加醒目留言之前调用,否则会导致头像无法加载
     *
     * @param portraitLoader
     */
    public void setPortraitLoader(PortraitLoader portraitLoader) {
        this.portraitLoader = portraitLoader;
    }

    /**
     * 设置计时监听接口
     *
     * @param timingListener
     */
    public void setTimingListener(OnTimingListenerAdapter timingListener) {
        this.timingListener = timingListener;
    }

    /**
     * 排序比较器
     * <p>
     * 创建醒目留言时会根据该接口进行排序
     */
    public interface SortComparator {

        /**
         * 比较方法
         * 返回true则item1排在前面
         * 返回false则item2排在前面
         *
         * @param item1
         * @param item2
         * @return
         */
        boolean onCompare(MessageItem item1, MessageItem item2);

    }

    /**
     * 点击监听接口
     */
    public interface OnMessageClickListener {

        /**
         * 点击回调方法
         *
         * @param position    醒目留言位置
         * @param percentage  剩余计时进度百分比
         * @param messageItem 数据对象
         */
        void onMessageClick(int position, float percentage, MessageItem messageItem);

    }

    /**
     * 头像加载接口
     */
    public interface PortraitLoader {

        /**
         * 头像加载回调
         *
         * @param position          醒目留言位置
         * @param messageItem       数据对象
         * @param portraitImageView 头像
         */
        void onLoad(int position, MessageItem messageItem, ImageView portraitImageView);

    }

    /**
     * 计时监听接口
     */
    private interface OnTimingListener {

        /**
         * 计时开始时回调
         *
         * @param position    醒目留言位置
         * @param messageItem 数据对象
         */
        void onStart(int position, MessageItem messageItem);

        /**
         * 计时过程中回调
         *
         * @param position    醒目留言位置
         * @param messageItem 数据对象
         * @param currentTime 当前计时时间
         * @param percentage  计时剩余进度百分比
         */
        void onTiming(int position, MessageItem messageItem, float currentTime, float percentage);

        /**
         * 计时结束时回调
         *
         * @param position    醒目留言位置
         * @param messageItem 数据对象
         */
        void onFinish(int position, MessageItem messageItem);

        /**
         * 计时取消时回调
         *
         * @param position    醒目留言位置
         * @param messageItem 数据对象
         */
        void onCancel(int position, MessageItem messageItem);

    }

    /**
     * 计时监听接口适配器,作用是让调用者自助选择需要监听的回调方法
     */
    public static class OnTimingListenerAdapter implements OnTimingListener {

        @Override
        public void onStart(int position, MessageItem messageItem) {

        }

        @Override
        public void onTiming(int position, MessageItem messageItem, float currentTime, float percentage) {

        }

        @Override
        public void onFinish(int position, MessageItem messageItem) {

        }

        @Override
        public void onCancel(int position, MessageItem messageItem) {

        }
    }

    private int dp2px(int dpVal) {

        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());
    }

    /**
     * 数据对象,用于封装醒目留言相关数据
     */
    public static class MessageItem {

        // 识别id
        private int viewId;

        // 留言内容
        private String message;

        // 计时总时间
        private float totalTime;

        // 计时起始时间,如totalTime=100s,initialTime=60s,则表示从剩余进度60%处开始计时
        private float initialTime;

        // 自定义存储对象,调用者可根据需要自行存储相关数据
        private Object extra;

        public MessageItem(int viewId, String message, float totalTime) {
            this(viewId, message, totalTime, totalTime, null);
        }

        public MessageItem(int viewId, String message, float totalTime, Object extra) {
            this(viewId, message, totalTime, totalTime, extra);
        }

        public MessageItem(int viewId, String message, float totalTime, float initialTime) {
            this(viewId, message, totalTime, initialTime, null);
        }

        public MessageItem(int viewId, String message, float totalTime, float initialTime, Object extra) {
            this.viewId = viewId;
            this.message = message;
            this.totalTime = totalTime;
            this.initialTime = initialTime;
            this.extra = extra;
        }

        public int getViewId() {
            return viewId;
        }

        public String getMessage() {
            return message;
        }

        public float getTotalTime() {
            return totalTime;
        }

        public float getInitialTime() {
            return initialTime;
        }

        public Object getExtra() {
            return extra;
        }

        void setInitialTime(float initialTime) {
            this.initialTime = initialTime;
        }
    }

    /**
     * 醒目留言可设置参数配置对象,用于封装所有醒目留言统一的可设置参数
     * 其中各个参数的意义具体参见EyesCatchingMessageView中的说明
     */
    public static class EyesCatchingMessageConfig {

        private Integer widthMode;
        private Integer width;
        private Integer height;
        private Integer portraitRadius;
        private Integer backgroundViewColor;
        private Integer timingViewColor;
        private Integer messageTextSize;
        private Integer messageTextColor;
        private Integer messageLeftMargin;
        private Integer messageRightMargin;
        private Integer messageLengthLimit;
        private Boolean showMessageText;

        public void setWidthMode(Integer widthMode) {
            this.widthMode = widthMode;
        }

        public void setWidth(Integer width) {
            this.width = width;
        }

        public void setHeight(Integer height) {
            this.height = height;
        }

        public void setPortraitRadius(Integer portraitRadius) {
            this.portraitRadius = portraitRadius;
        }

        public void setBackgroundViewColor(Integer backgroundViewColor) {
            this.backgroundViewColor = backgroundViewColor;
        }

        public void setTimingViewColor(Integer timingViewColor) {
            this.timingViewColor = timingViewColor;
        }

        public void setMessageTextSize(Integer messageTextSize) {
            this.messageTextSize = messageTextSize;
        }

        public void setMessageTextColor(Integer messageTextColor) {
            this.messageTextColor = messageTextColor;
        }

        public void setMessageLengthLimit(Integer messageLengthLimit) {
            this.messageLengthLimit = messageLengthLimit;
        }

        public void setMessageLeftMargin(Integer messageLeftMargin) {
            this.messageLeftMargin = messageLeftMargin;

        }

        public void setMessageRightMargin(Integer messageRightMargin) {
            this.messageRightMargin = messageRightMargin;
        }

        public void setShowMessageText(Boolean showMessageText) {
            this.showMessageText = showMessageText;
        }

        /**
         * 配置对象建造者,用于让调用者方便地创建一个配置对象实例
         */
        public static class EyesCatchingMessageConfigBuilder {

            private EyesCatchingMessageConfig config;

            public EyesCatchingMessageConfigBuilder() {
                config = new EyesCatchingMessageConfig();
            }

            public EyesCatchingMessageConfigBuilder setWidthMode(Integer widthMode) {
                config.setWidthMode(widthMode);

                return this;
            }

            public EyesCatchingMessageConfigBuilder setWidth(Integer width) {
                config.setWidth(width);

                return this;
            }

            public EyesCatchingMessageConfigBuilder setHeight(Integer height) {
                config.setHeight(height);

                return this;
            }

            public EyesCatchingMessageConfigBuilder setPortraitRadius(Integer portraitRadius) {
                config.setPortraitRadius(portraitRadius);

                return this;
            }

            public EyesCatchingMessageConfigBuilder setBackgroundViewColor(Integer backgroundViewColor) {
                config.setBackgroundViewColor(backgroundViewColor);

                return this;
            }

            public EyesCatchingMessageConfigBuilder setTimingViewColor(Integer timingViewColor) {
                config.setTimingViewColor(timingViewColor);

                return this;
            }

            public EyesCatchingMessageConfigBuilder setMessageTextSize(Integer messageTextSize) {
                config.setMessageTextSize(messageTextSize);

                return this;
            }

            public EyesCatchingMessageConfigBuilder setMessageTextColor(Integer messageTextColor) {
                config.setMessageTextColor(messageTextColor);

                return this;
            }

            public EyesCatchingMessageConfigBuilder setMessageLengthLimit(Integer messageLengthLimit) {
                config.setMessageLengthLimit(messageLengthLimit);

                return this;
            }

            public EyesCatchingMessageConfigBuilder setMessageLeftMargin(Integer messageLeftMargin) {
                config.setMessageLeftMargin(messageLeftMargin);

                return this;

            }

            public EyesCatchingMessageConfigBuilder setMessageRightMargin(Integer messageRightMargin) {
                config.setMessageRightMargin(messageRightMargin);

                return this;
            }

            public EyesCatchingMessageConfigBuilder setShowMessageText(Boolean showMessageText) {
                config.setShowMessageText(showMessageText);

                return this;
            }

            /**
             * 返回创建好的配置对象
             *
             * @return
             */
            public EyesCatchingMessageConfig create() {
                return config;
            }
        }
    }
}

EyesCatchingMessageLayout继承自LinearLayout,是一个布局存放所有的醒目留言EyesCatchingMessageView对象,并提供了一系列方法实现对这些对象的增加、删除、管理等等功能。

又废话了那么多不如实际演练一下。先在布局里加上这个控件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.min.eyescatchingmessageview.view.EyesCatchingMessageLayout
        android:id="@+id/message_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#eeeeee" />

    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <EditText
            android:id="@+id/money_edittext"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_margin="10dp"
            android:hint="输入金额"
            android:inputType="number"
            android:maxLines="1" />

        <EditText
            android:id="@+id/totaltime_edittext"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_margin="10dp"
            android:hint="输入时间"
            android:inputType="number"
            android:maxLines="1" />

        <EditText
            android:id="@+id/initialtime_edittext"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_margin="10dp"
            android:hint="输入初始时间"
            android:inputType="number"
            android:maxLines="1" />

        <Button
            android:id="@+id/send_button"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_margin="10dp"
            android:text="发送" />

    </LinearLayout>

</LinearLayout>

然后在Activity里写点简单的交互逻辑:

private EyesCatchingMessageLayout messageLayout;
private EditText moneyEditText, totalTimeEditText, initialTimeEditText;
private Button sendButton;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    intiView();
    initMessageLayout();
}

private void intiView() {
    messageLayout = findViewById(R.id.message_layout);

    moneyEditText = findViewById(R.id.money_edittext);
    totalTimeEditText = findViewById(R.id.totaltime_edittext);
    initialTimeEditText = findViewById(R.id.initialtime_edittext);
    sendButton = findViewById(R.id.send_button);

    sendButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int money;
            int totalTime;
            Integer initialTime = null;

            if (!moneyEditText.getText().toString().isEmpty()
                    && !totalTimeEditText.getText().toString().isEmpty()) {
                money = Integer.parseInt(moneyEditText.getText().toString());
                totalTime = Integer.parseInt(totalTimeEditText.getText().toString());

                if (!initialTimeEditText.getText().toString().isEmpty()) {
                    initialTime = Integer.parseInt(initialTimeEditText.getText().toString());
                }

                messageLayout.addMessageView(new EyesCatchingMessageLayout.MessageItem(
                        messageLayout.getMessageViewCount(),
                        "¥" + money,
                        totalTime,
                        initialTime == null ? totalTime : initialTime.intValue(),
                        money
                ));

                moneyEditText.setText("");
                totalTimeEditText.setText("");
                initialTimeEditText.setText("");
            } else {
                Toast.makeText(MainActivity.this, "INVALID INPUT!", Toast.LENGTH_SHORT).show();
            }
        }
    });
}

private void initMessageLayout() {
    messageLayout.setPortraitLoader(new EyesCatchingMessageLayout.PortraitLoader() {
        @Override
        public void onLoad(int position, EyesCatchingMessageLayout.MessageItem messageItem, ImageView portraitImageView) {
            portraitImageView.setImageResource(R.drawable.huaji);
        }
    });
    messageLayout.setTimingListener(new EyesCatchingMessageLayout.OnTimingListenerAdapter() {
        @Override
        public void onFinish(int position, EyesCatchingMessageLayout.MessageItem messageItem) {
            Toast.makeText(MainActivity.this, "第" + position + "条醒目留言:" + messageItem.getMessage() + "  显示时间结束了!", Toast.LENGTH_SHORT).show();
        }
    });
    messageLayout.setClickListener(new EyesCatchingMessageLayout.OnMessageClickListener() {
        @Override
        public void onMessageClick(int position, float percentage, EyesCatchingMessageLayout.MessageItem messageItem) {

        }
    });
}

@Override
protected void onDestroy() {
    super.onDestroy();

    if (messageLayout != null) {
        messageLayout.cancelAllTiming();
    }
}

EyesCatchingMessageLayout提供的各种操作醒目留言对象的方法,源码注释都写得比较清楚了,这里就提几个比较重要的:

addMessageView():添加一个醒目留言
addMessageViews():批量添加一组醒目留言
cancelTimingByViewId():取消指定的醒目留言的计时
cancelAllTiming():取消所有的醒目留言的计时,一般在页面退出时执行此方法。
setPortraitLoader():设置头像加载器,用法同EyesCatchingMessageView,注意此方法需在调用addMessageView方法添加醒目留言之前调用,否则会导致头像无法加载
setClickListener():设置醒目留言的点击事件。

好了终于可以看一下效果了,运行一下:

EyesCatchingMessageLayout同样支持对醒目留言的各种参数设定,通过参数配置类EyesCatchingMessageConfig实现,可以通过内部建造器EyesCatchingMessageConfigBuilder快速创建一个Config对象,添加几行代码:

private void initMessageLayout() {
    EyesCatchingMessageLayout.EyesCatchingMessageConfig config = new EyesCatchingMessageLayout.EyesCatchingMessageConfig.EyesCatchingMessageConfigBuilder()
            .setHeight(80)
            .setPortraitRadius(30)
            .setBackgroundViewColor(Color.parseColor("#70FFD700"))
            .setTimingViewColor(Color.parseColor("#FFD700"))
            .setMessageTextSize(20)
            .setMessageTextColor(Color.BLACK)
            .setMessageLengthLimit(8)
            .setMessageLeftMargin(10)
            .setMessageRightMargin(10)
            .create();

    messageLayout.setConfig(config);
    messageLayout.setMessagePadding(30, 30, 30, 30);
    messageLayout.setMessageMargin(20);
    
    ......
}

用法很简单,就是new一个EyesCatchingMessageConfigBuilder对象,然后set各种参数,然后调用create方法创建一个Config对象,最后调用EyesCatchingMessageLayout的setConfig方法完成参数配置。EyesCatchingMessageConfigBuilder下各种set方法的意义和EyesCatchingMessageView中的一样,大家看注释就好。

看看效果:

控件支持对醒目留言排序的功能。通过排序比较器接口SortComparator实现,其默认实现为越新的醒目留言排序越靠前。可以通过setSortComparator方法自定义排序规则。比如说,现在的规则是,金额越大的显示越靠前,金额相同越新的显示越靠前,就可以这样写:

private void initMessageLayout() {
    
    ......

    messageLayout.setSortComparator(new EyesCatchingMessageLayout.SortComparator() {
        @Override
        public boolean onCompare(EyesCatchingMessageLayout.MessageItem item1, EyesCatchingMessageLayout.MessageItem item2) {

            int money1 = (int) item1.getExtra();
            int money2 = (int) item2.getExtra();

            return money1 >= money2;
        }
    });
    
    ......
    
}

看看效果:

好了,到此为止所有的内容都介绍完了,最后来总结一下:

●    EyesCatchingMessageView实现了醒目留言的基本功能,可以通过EyesCatchingMessageViewBuilder来快速构建。
●    EyesCatchingMessageLayout实现了对EyesCatchingMessageView的集成管理,包括增加、删除、排序、过程监听等等。
●    通过配置类EyesCatchingMessageConfig完成对醒目留言的统一参数配置,
EyesCatchingMessageConfig可通过建造器EyesCatchingMessageConfigBuilder来快速构造。

 

最后的最后,附上源码地址:https://download.csdn.net/download/Sure_Min/12579376

 

这次的内容就到这里,我们下次再见。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值