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,请大家指正!
上一篇: Js实现简单的表单验证(小白篇)