轻量级控件SnackBar应用&源码分析
前言
SnackBar是AndroidSupportDesignLibrary库支持的一个控件,它在使用的时候经常和CoordinatorLayout一起使用,它是介于Toast和Dialog之间的产物,属于轻量级控件很方便的提供提示和动作反馈,有时候我们需要这样的控件,和Toast一样显示便可以消失,又想这个消息提示上进行用户的反馈。然而写Dialog只能通过点击去取消它,所以SnackBar的出现更加让界面优雅。
Part1、SnackBar的常规使用
[java]viewplaincopy
Snackbarsnackbar=Snackbar.make(v,R.string.tip,Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(R.string.know,newView.OnClickListener(){
@Override
publicvoidonClick(Viewv){
Toast.makeText(MainActivity.this,"clickknow",Toast.LENGTH_SHORT).show();
}
});
snackbar.setCallback(newSnackbar.Callback(){
@Override
publicvoidonDismissed(Snackbarsnackbar,intevent){
super.onDismissed(snackbar,event);
Toast.makeText(MainActivity.this,"onDismissed",Toast.LENGTH_SHORT).show();
}
@Override
publicvoidonShown(Snackbarsnackbar){
super.onShown(snackbar);
Toast.makeText(MainActivity.this,"onShown",Toast.LENGTH_SHORT).show();
}
});
snackbar.setActionTextColor(Color.GREEN);
snackbar.show();
}
效果~
tips:
1、Snackbar.LENGTH_INDEFINITE:无穷时间
2、SnackBar不能添加多个Action,当添加多个时,后一个会覆盖前一个
3、如果要监听SnackBar的显示和消失则设置setCallback
Part2、SnackBar源码分析
SnackBar类make方法
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
@NonNull
publicstaticSnackbarmake(@NonNullViewview,@NonNullCharSequencetext,
@Durationintduration){
Snackbarsnackbar=newSnackbar(findSuitableParent(view));
snackbar.setText(text);
snackbar.setDuration(duration);
returnsnackbar;
}
构造方法
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
privateSnackbar(ViewGroupparent){
mTargetParent=parent;
mContext=parent.getContext();
ThemeUtils.checkAppCompatTheme(mContext);
LayoutInflaterinflater=LayoutInflater.from(mContext);
mView=(SnackbarLayout)inflater.inflate(
R.layout.design_layout_snackbar,mTargetParent,false);
mAccessibilityManager=(AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
}
tips:
1、这里传入的view是一个锚点,然而在构造方法里面将findSuitableParent()返回的父类传入构造方法中
2、这里看一下R.layout.design_layout_snackbar.xml
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
class="android.support.design.widget.Snackbar$SnackbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
style="@style/Widget.Design.Snackbar"/>
这里使用了自定义View,路径为class=“android.support.design.widget.Snackbar&SnackbarLayout”;此为内部类,这里先不分析此类。
通过使用inflate方法将此View进行填充,由于最后一个参数为false,所以在SnackBar类肯定会有addView方法将此View添加到parent中
findSuitableParent()方法
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
privatestaticViewGroupfindSuitableParent(Viewview){
ViewGroupfallback=null;
do{
if(viewinstanceofCoordinatorLayout){
//We''vefoundaCoordinatorLayout,useit
return(ViewGroup)view;
}elseif(viewinstanceofFrameLayout){
if(view.getId()==android.R.id.content){
//Ifwe''vehitthedecorcontentview,thenwedidn''tfindaCoLinthe
//hierarchy,souseit.
return(ViewGroup)view;
}else{
//It''snotthecontentviewbutwe''lluseitasourfallback
fallback=(ViewGroup)view;
}
}
if(view!=null){
//Else,wewillloopandcrawluptheviewhierarchyandtrytofindaparent
finalViewParentparent=view.getParent();
view=parentinstanceofView?(View)parent:null;
}
}while(view!=null);
//Ifwereachherethenwedidn''tfindaCoLorasuitablecontentviewsowe''llfallback
returnfallback;
}
tip:
这里为了找到描点的父容器,然而在此方法中使用了while循环将不断的查找父级控件等于CoordinatorLayout或者FrameLayout(也就是DecorView),并将得到的parent返回。
根据代码,将会执行setAction方法
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
@NonNull
publicSnackbarsetAction(CharSequencetext,finalView.OnClickListenerlistener){
finalTextViewtv=mView.getActionView();
if(TextUtils.isEmpty(text)||listener==null){
tv.setVisibility(View.GONE);
tv.setOnClickListener(null);
}else{
tv.setVisibility(View.VISIBLE);
tv.setText(text);
tv.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
listener.onClick(view);
//NowdismisstheSnackbar
dispatchDismiss(Callback.DISMISS_EVENT_ACTION);
}
});
}
returnthis;
}
这里只不过是将上面所说的那个自定义View设置内容
继续向下执行,为SnackBar的show方法
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
/
Showthe{@linkSnackbar}.
/
publicvoidshow(){
SnackbarManager.getInstance().show(mDuration,mManagerCallback);
}
这里又涉及到了一个新类SnackBarManager,从字面意思可只是一个管理SnackBar的类
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicvoidshow(intduration,Callbackcallback){
synchronized(mLock){
if(isCurrentSnackbarLocked(callback)){//(1)
//Meansthatthecallbackisalreadyinthequeue.We''lljustupdatetheduration
mCurrentSnackbar.duration=duration;
//IfthisistheSnackbarcurrentlybeingshown,callre-scheduleit''s
//timeout
mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
scheduleTimeoutLocked(mCurrentSnackbar);
return;
}elseif(isNextSnackbarLocked(callback)){//(2)
//We''lljustupdatetheduration
mNextSnackbar.duration=duration;
}else{//(3)
//Else,weneedtocreateanewrecordandqueueit
mNextSnackbar=newSnackbarRecord(duration,callback);
}
if(mCurrentSnackbar!=null&&cancelSnackbarLocked(mCurrentSnackbar,
Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)){//(4)
//IfwecurrentlyhaveaSnackbar,tryandcancelitandwaitinline
return;
}else{//(5)
//Clearoutthecurrentsnackbar
mCurrentSnackbar=null;
//Otherwise,justshowitnow
showNextSnackbarLocked();
}
}
}
这里为了分析方便在每个判断都加上了标号
tips:
1、(1)中判断如果是当前SnackBar锁定则先将Handler移除队列中CallBack,然后在添加,这样就避免了等待,请注意这里用到的类是SnackbarRecord而不是SnackBar
2、(2)如果是下一个SnackBar则将Duration更新
ok,show()方法中传入了CallBack对象
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
interfaceCallback{
voidshow();
voiddismiss(intevent);
}
这里也就是这个接口,相应实现接口在哪里呢?这里我们回退到SnackBar的show()方法中传入了mManagerCallback,然而经过查找在SnackBar类中就有相应的实现
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
finalSnackbarManager.CallbackmManagerCallback=newSnackbarManager.Callback(){
@Override
publicvoidshow(){
sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW,Snackbar.this));
}
@Override
publicvoiddismiss(intevent){
sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS,event,0,Snackbar.this));
}
};
到这里我们应该清楚:SnackBar和SnackBarManager之间通过CallBack来实现通信,而SnackBarManager维护的是Callback类。
由于上面添加的是是SnackBarRecord而不是SnackBar,来研究一下
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
privatestaticclassSnackbarRecord{
finalWeakReferencecallback;
intduration;
SnackbarRecord(intduration,Callbackcallback){
this.callback=newWeakReference<>(callback);
this.duration=duration;
}
booleanisSnackbar(Callbackcallback){
returncallback!=null&&this.callback.get()==callback;
}
}
tips:
只是存放了Callback和duration,这里值得学习是使用了弱引用类型来存储Callback,减少了ANR异常
这里对于SnackBar的流程大致完毕,对于回调类中show和dismiss方法,我们来看一下内部是如何进行处理的呢?
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
static{
sHandler=newHandler(Looper.getMainLooper(),newHandler.Callback(){
@Override
publicbooleanhandleMessage(Messagemessage){
switch(message.what){
caseMSG_SHOW:
((Snackbar)message.obj).showView();
returntrue;
caseMSG_DISMISS:
((Snackbar)message.obj).hideView(message.arg1);
returntrue;
}
returnfalse;
}
});
}
showView()方法
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
finalvoidshowView(){
if(mView.getParent()==null){
finalViewGroup.LayoutParamslp=mView.getLayoutParams();
......
}
mTargetParent.addView(mView);
.......//(2)
p;}
ok,通过SnackBarManager调用Callback接口的show方法将View添加到了Parent中
在来看省略号(2)中的代码
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
mView.setOnAttachStateChangeListener(newSnackbarLayout.OnAttachStateChangeListener(){
@Override
publicvoidonViewAttachedToWindow(Viewv){}
@Override
publicvoidonViewDetachedFromWindow(Viewv){
if(isShownOrQueued()){
//Ifwehaven''talreadybeendismissedthenthiseventiscomingfroma
//non-userinitiatedaction.Henceweneedtomakesurethatwecallback
//andkeepourstateuptodate.WeneedtopostthecallsinceremoveView()
//willcallthroughtoonDetachedFromWindowandthusoverflow.
sHandler.post(newRunnable(){
@Override
publicvoidrun(){
onViewHidden(Callback.DISMISS_EVENT_MANUAL);
}
});
}
}
});
if(ViewCompat.isLaidOut(mView)){
if(shouldAnimate()){
//Ifanimationsareenabled,animateitin
animateViewIn();
}else{
//Elseifanimsaredisabledjustcallbacknow
onViewShown();
}
}else{
//Otherwise,addoneofourlayoutchangelistenersandshowitinwhenlaidout
mView.setOnLayoutChangeListener(newSnackbarLayout.OnLayoutChangeListener(){
@Override
publicvoidonLayoutChange(Viewview,intleft,inttop,intright,intbottom){
mView.setOnLayoutChangeListener(null);
if(shouldAnimate()){
//Ifanimationsareenabled,animateitin
animateViewIn();
}else{
//Elseifanimsaredisabledjustcallbacknow
onViewShown();
}
}
});
}
tips:
view.setOnAttachStateChangeListener():监听View视图关联状态发生改变
来看一下animateViewIn()
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
voidanimateViewIn(){
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.ICE_CREAM_SANDWICH){
ViewCompat.setTranslationY(mView,mView.getHeight());
ViewCompat.animwww.baiyuewang.netate(mView)
.translationY(0f)
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
.setDuration(ANIMATION_DURATION)
.setListener(newViewPropertyAnimatorListenerAdapter(){
@Override
publicvoidonAnimationStart(Viewview){
mView.animateChildrenIn(ANIMATION_DURATION-ANIMATION_FADE_DURATION,
ANIMATION_FADE_DURATION);
}
@Override
publicvoidonAnimationEnd(Viewview){
onViewShown();
}
}).start();
}else{
Animationanim=AnimationUtils.loadAnimation(mView.getContext(),
R.anim.design_snackbar_in);
anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
anim.setDuration(ANIMATION_DURATION);
anim.setAnimationListener(newAnimation.AnimationListener(){
@Override
publicvoidonAnimationEnd(Animationanimation){
onViewShown();
}
@Override
publicvoidonAnimationStart(Animationanimation){}
@Override
publicvoidonAnimationRepeat(Animationanimation){}
});
mView.startAnimation(anim);
}
}
这里分为大于3.0和小于3.0,分别使用相应的动画来实现SnackBar显示的动画
最后我们来看一下最上面说到的SnackBar所使用的自定义View
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
/
@hide
/
@RestrictTo(GROUP_ID)
publicstaticclassSnackbarLayoutextendsLinearLayout{
继承了LinearLayout,默认情况为线性布局,这里只是添加了TextView和Button控件
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
//Nowinflateourcontent.Weneedtodothismanuallyratherthanusingan
//inthelayoutsinceolderversionsoftheAndroiddonotinflateincludeswith
//thecorrectContext.
LayoutInflater.from(context).inflate(R.layout.design_layout_snackbar_include,this);
这里没有用include是为了兼容低版本,root是this意味将该布局添加到这个View中
design_layout_snackbar_include.xml:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
android:id="@+id/snackbar_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingTop="@dimen/design_snackbar_padding_vertical"
android:paddingBottom="@dimen/design_snackbar_padding_vertical"
android:paddingLeft="@dimen/design_snackbar_padding_horizontal"
android:paddingRight="@dimen/design_snackbar_padding_horizontal"
android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"
android:maxLines="@integer/design_snackbar_text_max_lines"
android:layout_gravity="center_vertical|left|start"
android:ellipsize="end"
android:textAlignment="viewStart"/>
|
|