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

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

程序员文章站 2022-05-31 14:11:02
...

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

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

其实咱们在平时使用APP时候经常见到这样的提示,比如腾讯新闻刷新后给出的数据更新提示:
Android UI界面中短暂消息提示实现

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

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

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

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

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

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

Android UI界面中短暂消息提示实现
优点:一行代码就可以出现上图效果。可以在任何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);

放一下效果图:
刷新提示:
Android UI界面中短暂消息提示实现
编辑提示:
Android UI界面中短暂消息提示实现
任一View下提示:
Android UI界面中短暂消息提示实现

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

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,请大家指正!

相关标签: 界面