配色: 字号:
轻量级控件SnackBar应用&源码分析
2017-01-20 | 阅:  转:  |  分享 
  
轻量级控件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"/>




android:id="@+id/snackbar_action"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginLeft="@dimen/design_snackbar_extra_spacing_horizontal"

android:layout_marginStart="@dimen/design_snackbar_extra_spacing_horizontal"

android:layout_gravity="center_vertical|right|end"

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:visibility="gone"

android:textColor="?attr/colorAccent"

style="?attr/borderlessButtonStyle"/>





至此,SnackBar的源码分析完毕

献花(0)
+1
(本文系thedust79首藏)