Android UI界面中短暂消息提示实现

最近做的项目里面遇到这么个需求:

在界面数据发生变化 | 手动点击了某个VIew | EditText提示输入的情况下给出一个 Tips
Tips要求:1 延迟一段时间后自动消失
2 触摸Tips View 之外的界面 自己自动消失
3 可以添加回调Action
4 图片和文字提示
5 要求有自定义动画

其实咱们在平时使用APP时候经常见到这样的提示,比如腾讯新闻刷新后给出的数据更新提示:
这里写图片描述

看这个应用的提示出现然后消失的现象,应该是 头部刷新布局里面的一个GONE掉的布局 ,在刷新成功后 以预置的动画Style出现然后消失。
也有可能是个自带动画策略 时间控制的自定义布局。

我暂时想了几个方法来实现这个效果:
1 动效控制GONE的预置布局
优点:好实现 缺点:适用情况单一 多个地方需要多次处理

这里写图片描述

  需要在当前布局 控制TipsView 的出现和消失。

2 自定义TipsView布局 刷新界面可以共用这个Tips布局
缺点:显示的地方都要引入该布局 不灵活 优点:可以体会下简单的自定义布局 (主要是处理时间和动画事件)

3 动态添加 View.addView()
SnakerBar 就是动态添加的View 。可以仿照SnakerBar的实现来做[下一步目标计划]
比如在RelativeLayout 中addView 设置Gravity 为Top 或者 Bottom。
这里写图片描述
4 因为要满足更多的特性 我决定在PopupWindow上作文章
需要写一个控制类来控制出现和消失时间、处理触摸事件和处理contentView

这里写图片描述
优点:一行代码就可以出现上图效果。可以在任何View的的下方出现。保持和锚点View同样的宽度。
这也就可以同时实现输入框提示了!

我们来看使用风格:

//带有回调的提示    
new SnakerPopView.ContentBuilder(getActivity(), false)
            .setText("提示信息~~")
            .setBackgroundColor(R.color.bg_color_deep)
            .setIcon(R.drawable.ic_launcher)
            .setContentViewAnimationStyle(R.style.SnakerPopWindowAnimation)
            .setSnakerPopViewHeight(150)//dp
            .build()
            .updateAndShowWithAction(mTabs, new SnakerPopView.ActionCallBack() {


                @Override
                public void onAction() {
                    Toast.makeText(getActivity(),"Action",Toast.LENGTH_LONG).show();


                }
            }




);

//延迟提示
new SnakerPopView.ContentBuilder(mContext,true).setText("提示信息~~").build().updateAndShowWithDelay(v,2000);

放一下效果图:
刷新提示:
这里写图片描述
编辑提示:
这里写图片描述
任一View下提示:
这里写图片描述

啰嗦了这么多,直接撸代码:

public class SnakerPopView {
    //Hanler消息
    private final static int DISSMISS_POPWINDOW = 0;
    //类属性 多个实例共用的属性 不易多次初始化
    //因为界面上显示线程比较单一 不存在多线程共用 所以不进行同步控制
    private static PopupWindowTouchListener touchListener;
    private static TimerHandler timerHander = new TimerHandler();
    private static PopupWindow popupWindow;
    private static View content;
    private static TextView text;
    private static ImageView icon;


    private boolean isChanged;
    //如果没有设置高度 则默认是80dp
    private int DEFAULT_HEIGHT = 80;//dp
    private ActionCallBack callBack;


    //用作延迟消失消息处理
    static class TimerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISSMISS_POPWINDOW:
                    dismiss();
                    break;
            }
        }
    }
    //私有构造器 使用ContentBuilder 创建
    private SnakerPopView(ContentBuilder contentBuilder) {


        //popupWindow实例共用属性 随类加载一次
        if (popupWindow == null) {
            Log.d("SnakerPopView", "popupWindow init");
            popupWindow = new PopupWindow(LinearLayout.LayoutParams.WRAP_CONTENT, contentBuilder.snakerPopViewHeight == 0 ? DEFAULT_HEIGHT : contentBuilder.snakerPopViewHeight);
            popupWindow.setOutsideTouchable(true);
        }
        //content view实例共用属性 随类加载一次
        if (content == null) {
            Log.d("SnakerPopView", "content init");
            content = LayoutInflater.from(contentBuilder.context).inflate(R.layout.snaker_pop_view, null);
            icon = (ImageView) content.findViewById(R.id.pop_icon);
            text = (TextView) content.findViewById(R.id.pop_text);
        }




        if (contentBuilder.isChanged) {
            Log.d("SnakerPopView", "isChanged");
            //设置提示文本
            text.setText(contentBuilder.text);
            //设置图片icon
            if (contentBuilder.imageResource != 0) {
                icon.setBackgroundResource(contentBuilder.imageResource);
                icon.setVisibility(View.VISIBLE);
            } else {
                icon.setVisibility(View.GONE);
            }
            //设置背景颜色 默认为R.color.colorAccent
            content.setBackgroundColor(contentBuilder.context.getResources().getColor(contentBuilder.colorResource == 0 ? R.color.colorAccent : contentBuilder.colorResource));
            popupWindow.setContentView(content);
            //设置显示和消失动画  默认R.style.SnakerPopWindowAnimation
            popupWindow.setAnimationStyle(contentBuilder.animationStyle == 0 ? R.style.SnakerPopWindowAnimation : contentBuilder.animationStyle);
        }
        //设置了提示消息不变也要更新显示 或者 消息等内容改变提示显示
        this.isChanged = contentBuilder.isChanged || contentBuilder.textNoChangeEfectEnable;


    }

    class PopupWindowTouchListener implements View.OnTouchListener {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.d("SnakerPopView", "onTouch"+event.getAction());
            switch (event.getAction()) {


                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
                case MotionEvent.ACTION_UP:
                    dismiss();
                    callBack.onAction();
                    break;
                case MotionEvent.ACTION_OUTSIDE:
                    remove();
                    break;
            }
            return false;
        }
    }


    public interface ActionCallBack {
        void onAction();
    }
    //清除显示 
    private static void dismiss() {
        popupWindow.dismiss();
    }
    //清除显示|延迟消失消息  立即消失
    public static void clear() {
        Log.d("SnakerPopView", "clear");
        timerHander.removeMessages(DISSMISS_POPWINDOW);
        popupWindow.setAnimationStyle(0);
        dismiss();
    }
    //清除显示|延迟消失消息 
    public static void remove() {
        timerHander.removeMessages(DISSMISS_POPWINDOW);
        dismiss();
    }


    public static boolean isShowing() {
        if (popupWindow == null) return false;
        return popupWindow.isShowing();
    }


    /**
     * 手动取消的带回调提示
     *
     * @param aboveView 在这个View的下面
     * @param callback  需要点击的action回调
     */
    public void updateAndShowWithAction(View aboveView, ActionCallBack callback) {
        if (callback == null) {
            throw new IllegalArgumentException("You must set the ActionCallBack when call this method!");
        }
        this.callBack = callback;
        if (touchListener == null) {
            touchListener = new PopupWindowTouchListener();
        }
        updateAndShow(aboveView, 0, touchListener);
    }


    /**
     * 延迟消失的提示
     *
     * @param aboveView
     * @param delayTime
     */
    public void updateAndShowWithDelay(View aboveView, long delayTime) {
        if (delayTime <= 0) {
            throw new IllegalArgumentException("The delayTime must be positive number!(>0)");
        }
        updateAndShow(aboveView, delayTime, null);
    }


    private void updateAndShow(View aboveView, long delayTime, View.OnTouchListener touchListener) {
        if (!isChanged) {
            Log.d("SnakerPopView", "no change return");
            return;
        }
        if (popupWindow.isShowing()) {
            Log.d("SnakerPopView", "changed  dismiss before");
            remove();
        }
        //设置弹出View触摸事件监听器
        if (touchListener != null) {
            content.setOnTouchListener(touchListener);
        }else{
            content.setOnTouchListener(null);
        }
        //保持弹出视图长度和锚点View长度一致
        popupWindow.setWidth(aboveView.getMeasuredWidth());
        popupWindow.showAsDropDown(aboveView);
        if (delayTime > 0) {
            timerHander.sendMessageDelayed(timerHander.obtainMessage(DISSMISS_POPWINDOW), delayTime);
        }


    }


    /**
     * The ContentBuilder class.
     */
    public static class ContentBuilder {
        public Context context;
        public static int imageResource;
        public static String text;
        public static int colorResource;
        public static int animationStyle;
        public static int snakerPopViewHeight;
        public boolean textNoChangeEfectEnable;
        public boolean isChanged;


        /**
         * @param context
         * @param textNoChangeEfectEnable 实际提示文本没有改变 if textNoChangeEfectEnable is ture:依然弹出
         *                                false:不弹出
         *                                textNoChangeEfectEnable is false    一般用在文本必定更改的情况下
         */
        public ContentBuilder(Context context, boolean textNoChangeEfectEnable) {
            this.textNoChangeEfectEnable = textNoChangeEfectEnable;
            this.context = context.getApplicationContext();
        }


        public ContentBuilder setIcon(int imageResourceFrom) {
            if (imageResourceFrom == 0) {
                throw new IllegalArgumentException("imageResourceFrom should not be 0");
            }
            if (imageResource != imageResourceFrom) {
                imageResource = imageResourceFrom;
                isChanged = true;
            }


            return this;
        }


        public ContentBuilder setText(String textFrom) {
            if (textFrom == null) {
                throw new IllegalArgumentException("null text");
            }
            if (!textFrom.equals(text)) {
                text = textFrom;
                isChanged = true;
            }


            return this;
        }


        public ContentBuilder setBackgroundColor(int colorResourceFrom) {


            if (colorResourceFrom == 0) {
                throw new IllegalArgumentException("colorResourceFrom should not be 0");
            }
            if (colorResource != colorResourceFrom) {
                colorResource = colorResourceFrom;
                isChanged = true;
            }
            return this;
        }


        public ContentBuilder setContentViewAnimationStyle(int animationStyleFrom) {


            if (animationStyleFrom == 0) {
                throw new IllegalArgumentException("animationStyleFrom should not be 0");
            }
            if (animationStyle != animationStyleFrom) {
                animationStyle = animationStyleFrom;
                isChanged = true;
            }
            return this;
        }


        public ContentBuilder setSnakerPopViewHeight(int snakerPopViewHeightFrom) {


            if (snakerPopViewHeightFrom == 0) {
                throw new IllegalArgumentException("snakerPopViewHeightFrom should not be 0");
            }
            if (snakerPopViewHeight != snakerPopViewHeightFrom) {
                snakerPopViewHeight = snakerPopViewHeightFrom;
                isChanged = true;
            }
            return this;
        }




        public SnakerPopView build() {
            return new SnakerPopView(this);
        }
    }


}
//内容布局
<?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="horizontal">




    <ImageView
        android:id="@+id/pop_icon"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="3" />


    <TextView
        android:id="@+id/pop_text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:text="TextView"
        android:textSize="18sp" />


</LinearLayout>

用了很多static 使得变量变成了类变量 总感觉有点违背 面向对象设计 理念。anyway,请大家指正!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值