0%

一些提示性的控件

提示性的控件代表了着一种典型的自定义控件的实现思路,即在某个布局上添加自定义的布局。

像系统定义的 Toast,Dialog,甚至 Activity 都是在 Window 上添加自定义布局,SnackBar 则是在 CoordinatorLayout 布局上添加自定义布局,以便利用自定义的 Behavior 类。

在具体实现上,不同的控件根据实现效果还要考虑 IPC 过程,动画效果,触摸事件等。

Toast

Toast 是一个系统提供的提示性控件,本质和 PopupWindow 一样,是在 Window 上添加和移除 View,所以最重要的是设置 View,否则显示的时候将出现异常。它的使用分为两步

1.构造。

常用的 makeText 方法即构造了一个只包括 TextView 的布局,如果要设置文本,必须将其 id 设置为android.R.id.message。

1
2
3
4
5
6
7
8
9
10
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
Toast result = new Toast(context);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);

result.mNextView = v;
result.mDuration = duration;
return result;
}

2.显示。

1
2
3
4
5
6
7
8
9
10
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
tn.mNextView = mNextView;
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
service.enqueueToast(pkg, tn, mDuration);
}

显示的过程是一个 IPC 过程,这是为了避免与其他应用的 Toast 冲突。显示 Toast 依靠 Handler 来实现,本质是通过 WindowManager 服务添加 mNextView 来实现的。Toast 可以配置的参数都应用在WindowManager.LayoutParams 上。 包括 gravity,偏移量,margin等。

1
2
3
WindowManager mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mParams.gravity = gravity;
mWM.addView(mView, mParams);

自定义View

因为可以设置 View,故而可以通过自定义 View 来实现各种效果,甚至可以通过 onAttachedToWindow 方法来获得动画效果。

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ValueAnimator va = ValueAnimator.ofInt(Color.GREEN, Color.CYAN).setDuration(1500);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
drawable.setColor((Integer) animation.getAnimatedValue());
}
});
va.start();
}

SnackBar

SnackBar 比 Toast 要轻量,它也要防止冲突,但并非通过 IPC 机制来完成。

首先是构建

1
public static Snackbar make(View view, int resId, @Duration int duration)

这里的 View 只用来寻找 Parent 布局,或者是 CoordinatorLayout 或者是 android.R.id.content,而后是加载 SnackbarLayout 到布局。

1
2
3
4
5
6
7
private final ViewGroup mTargetParent;
final SnackbarLayout mView;
private Snackbar(ViewGroup parent) {
mTargetParent = parent;
mView = (SnackbarLayout) inflater.inflate(
R.layout.design_layout_snackbar, mTargetParent, false);
}

这个 SnackbarLayout 是一个线性布局,它采用 LayoutInflate 将带 merge 标签的布局加载进自己

1
2
3
4
5
6
7
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:id="@+id/snackbar_text""/>

<Button android:id="@+id/snackbar_action"
android:textColor="?attr/colorAccent"
style="?attr/borderlessButtonStyle"/>
</merge>

这样 setAction 方法就是此 Button 点击事件的委托处理,点击将执行 dispatchDismiss 方法,

show 方法交给 SnackbarManager 来处理,这个类管理两个 SnackbarRecord 实例,以避免叠加

1
2
private SnackbarRecord mCurrentSnackbar;
private SnackbarRecord mNextSnackbar;

显示的时候,要取消当前,实例化和显示后者,SnackbarRecord 实际就是一个包装了功能的回调,其执行内容为 Callback 的 show 方法。真实的显示方法为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final void showView() {
if (mView.getParent() == null) {
final ViewGroup.LayoutParams lp = mView.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp;
Behavior behavior = new Behavior();
behavior.setListener(new SwipeDismissBehavior.OnDismissListener());
clp.setBehavior(behavior);
clp.insetEdge = Gravity.BOTTOM;
}
mTargetParent.addView(mView);
//使用动画
}
}

这里 SnackbarLayout 将配置上 SwipeDismissBehavior,该类决定了其行为。SwipeDismissBehavior 是一个提供 swipe-to-dismiss 的类,依靠 ViewDragHelper 实现,

FloatingToolbar

FloatingToolbar 是模仿 Snackbar 实现的一个库。

如果将动画放慢,就会发现首先 Fab 先消失(alpha动画),而后 FloatingToolbar 才扩展出现(scaleX动画),因此主要的工作是设置和添加布局。

FloatingToolbar 是一个线性布局,可以添加自定义布局,也可以添加菜单。Fab 的点击事件发生后,将执行动画显示;如果再点击菜单中的控件,将执行各自ItemClick,并执行隐藏操作。

FloatingToolbar 的优点还在于考虑了与Snackbar 冲突的情况,通过添加 OnScrollListener 来在 RecyclerView 滑动时隐藏 FloatingToolbar 。

FloatingToolbar 需要在自定义布局,虽然有自由度,但没有 Snackbar 封装的那么好,而且也没有利用Behavior类。

Fab 的点击事件发生后,将执行动画显示;如果再点击菜单中的控件,将执行各自ItemClick,并执行隐藏操作。

Alert

与之类似还有LoadToast, 原理是在 Window 上添加控件,并使用动画来显示。

通知

服务 NotificationManager 将通知展示在状态栏处,属于跨进程通信。

其中 Notification 类是 Parcelable 对象,包装了通知所需的数据,包括关于信息的,关于布局的。

1
2
3
4
5
6
7
8
9
10
11
12
13
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification compat = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.boot)
.setContentText("通知必须有:标题,内容和显示在状态栏的Small图标")
.setContentTitle("标题")
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.beauty))
.setSubText("子标题")
.setCustomBigContentView(remoteViews) //大图
.setAutoCancel(true) //点击取消
.setTicker("通知") //显示在状态栏的标题
.setDefaults(Notification.DEFAULT_ALL) //效果
.build();
manager.notify("like", 6, compat);

通知必须有:标题,内容和显示在状态栏的Small图标