栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Meterial Design常见控件的使用(三),2021大厂Android高级面试题及答案

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Meterial Design常见控件的使用(三),2021大厂Android高级面试题及答案

snackbar.setText(text);

snackbar.setDuration(duration);

return snackbar;

}

通过inflate获取到SnackBarContentLayout布局,SnackBarContentLayout实际上是一个LinearLayout,再来看看R.layout.design_layout_snackbar_include:

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"

/>

没错,这就是Snackbar的主要布局了,一个TextView和一个Button。

获取到的SnackbarContentLayout,通过实例化Snackbar,传进了Snackbar的构造方法中,最后到了Snackbar的父类baseTransientBottomBar的构造方法中:

protected baseTransientBottomBar(@NonNull ViewGroup parent, @NonNull View content,

@NonNull ContentViewCallback contentViewCallback) {

//省略不重要代码

mTargetParent = parent; //之前findSuitableParent方法找到的ViewGroup

//callback传进来的SnackbarContentLayout,其实现了ContentViewCallback接口

mContentViewCallback = contentViewCallback;

mContext = parent.getContext();

ThemeUtils.checkAppCompatTheme(mContext);

LayoutInflater inflater = LayoutInflater.from(mContext);

// Note that for backwards compatibility reasons we inflate a layout that is defined

// in the extending Snackbar class. This is to prevent breakage of apps that have custom

// coordinator layout behaviors that depend on that layout.

mView = (SnackbarbaseLayout) inflater.inflate(

R.layout.design_layout_snackbar, mTargetParent, false);

mView.addView(content);//将SnackbarContentLayout添加到SnackbarLayout中

…//省略剩余代码

}

@NonNull

public View getView() {

return mView;

}

已经备注很详细了,接着来看看R.layout.design_layout_snackbar:

class=“android.support.design.widget.Snackbar$SnackbarLayout”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:layout_gravity=“bottom”

/>

注意到class了吗,没错,此View是Snackbar中定义的SnackbarLayout,继承自baseTransientBottomBar中的SnackbarbaseLayout,而SnackbarbaseLayout继承自frameLayout。SnackbarLaout中只重新了onMeasure方法,其他实现都在SnackbarbaseLayout中。

还有一个关键的地方,layout_gravity被设置成了bottom,这也是为什么Snackbar总显示在底部的原因。

到这里我们已经知道了**Snackbar的布局实际上是一个frameLayout,其内容是一个LinearLayout。baseTransientBottomBar提供了getView方法来获取mView,mView即为Snackbar的根布局frameLayout。**既然能获取到根布局,那往此布局中addView肯定是没问题了,之前提到的问题3也可以利用这一点解决了。

show方法

为什么不是action,而是show。因为我关心的是Snackbar如何显示的。

public void show() {

SnackbarManager.getInstance().show(mDuration, mManagerCallback);

}

看到这里是不是有点蒙蔽了,怎么到了SnackbarManager的show方法了,不着急,我们先来看看mManagerCallback:

final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {

@Override

public void show() {

sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, baseTransientBottomBar.this));

}

@Override

public void dismiss(int event) {

sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0,

baseTransientBottomBar.this));

}

};

mManagerCallback内部使用了Handler来控制show和dismiss,最终sHandler会调用showView:

final void showView() {

if (mView.getParent() == null) {

final ViewGroup.LayoutParams lp = mView.getLayoutParams();

if (lp instanceof CoordinatorLayout.LayoutParams) {

// 如果LayoutParams是CoordinatorLayout的,就设置Behavior

final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp;

final Behavior behavior = new Behavior();

behavior.setStartAlphaSwipeDistance(0.1f);

behavior.setEndAlphaSwipeDistance(0.6f);

//设置SwipeDismissBehavior,具体作用就是滑动删除view behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);

behavior.setListener(new SwipeDismissBehavior.onDismissListener() {

@Override

public void onDismiss(View view) {

view.setVisibility(View.GONE);

dispatchDismiss(baseCallback.DISMISS_EVENT_SWIPE);

}

@Override

public void onDragStateChanged(int state) {

switch (state) {

case SwipeDismissBehavior.STATE_DRAGGING:

case SwipeDismissBehavior.STATE_SETTLING:

// If the view is being dragged or settling, pause the timeout

SnackbarManager.getInstance().pauseTimeout(mManagerCallback);

break;

case SwipeDismissBehavior.STATE_IDLE:

// If the view has been released and is idle, restore the timeout

SnackbarManager.getInstance()

.restoreTimeoutIfPaused(mManagerCallback);

break;

}

}

});

clp.setBehavior(behavior);

// Also set the inset edge so that views can dodge the bar correctly

clp.insetEdge = Gravity.BOTTOM;

}

//重点来了,mView被添加到了mTargetParent中,之前向上遍历view获取的ViewGroup

mTargetParent.addView(mView);

}

mView.setonAttachStateChangeListener(

new baseTransientBottomBar.onAttachStateChangeListener() {

@Override

public void onViewAttachedToWindow(View v) {}

@Override

public void onViewDetachedFromWindow(View v) {

if (isShownOrQueued()) {

// If we haven’t already been dismissed then this event is coming from a

// non-user initiated action. Hence we need to make sure that we callback

// and keep our state up to date. We need to post the call since

// removeView() will call through to onDetachedFromWindow and thus overflow.

sHandler.post(new Runnable() {

@Override

public void run() {

onViewHidden(baseCallback.DISMISS_EVENT_MANUAL);

}

});

}

}

});

if (ViewCompat.isLaidOut(mView)) {

if (shouldAnimate()) {

// If animations are enabled, animate it in

animateViewIn();

} else {

// Else if anims are disabled just call back now

onViewShown();

}

} else {

// Otherwise, add one of our layout change listeners and show it in when laid out

mView.setonLayoutChangeListener(new baseTransientBottomBar.onLayoutChangeListener() {

@Override

public void onLayoutChange(View view, int left, int top, int right, int bottom) {

mView.setonLayoutChangeListener(null);

if (shouldAnimate()) {

// If animations are enabled, animate it in

animateViewIn();

} else {

// Else if anims are disabled just call back now

onViewShown();

}

}

});

}

}

通过showView中的代码,终于了解到Snackbar是如何显示的。Snackbar被直接添加到mTargetParent中,就是make方法传递进来的View的父CoordinatorLayout或根布局。

根据Snackbar的布局文件我们知道其layout_gravity为bottom,也就是会显示在mTargetParent的底部。那我们是不是只要将一个有固定高度的CoordinatorLayout传递个给Snackbar的make,就可以改变Snackbar的显示位置了?答案是肯定的!

到这里我们的问题1和问题2都明了了。那问题4应该如何解决呢?

SnackbarManager

根据上面的分析,我们知道show方法会调用SnackbarManager的show方法,那我们就来看看SnackBarManager的源码:

class SnackbarManager {

static final int MSG_TIMEOUT = 0;

private static final int SHORT_DURATION_MS = 1500;

private static final int LONG_DURATION_MS = 2750;

private static SnackbarManager sSnackbarManager;

//单例模式

static SnackbarManager getInstance() {

if (sSnackbarManager == null) {

sSnackbarManager = new SnackbarManager();

}

return sSnackbarManager;

}

private final Object mLock;

private final Handler mHandler;

//用来存储当前显示Snackbar的duration和Callback

private SnackbarRecord mCurrentSnackbar;

//用来存储接下来要显示的Snackbar的duration和Callback

private SnackbarRecord mNextSnackbar;

private SnackbarManager() {

mLock = new Object();

mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {

@Override

public boolean handleMessage(Message message) {

switch (message.what) {

case MSG_TIMEOUT:

handleTimeout((SnackbarRecord) message.obj);

return true;

}

return false;

}

});

}

interface Callback {

void show();

void dismiss(int event);

}

public void show(int duration, Callback callback) {

synchronized (mLock) {

if (isCurrentSnackbarLocked(callback)) { //判断是否是当前显示的Snackbar,更新duration

// Means that the callback is already in the queue. We’ll just update the duration

mCurrentSnackbar.duration = duration;

// If this is the Snackbar currently being shown, call re-schedule it’s

// timeout

mHandler.removeCallbacksAndMessages(mCurrentSnackbar);//移除Callback,避免内存泄露

scheduleTimeoutLocked(mCurrentSnackbar);//重新关联设置duration和Callback

return;

} else if (isNextSnackbarLocked(callback)) { //判断是否是接下来要显示的Snackbar,更新duration

// We’ll just update the duration

mNextSnackbar.duration = duration;

} else {

// Else, we need to create a new record and queue it

mNextSnackbar = new SnackbarRecord(duration, callback);

}

if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar,

Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) {

// If we currently have a Snackbar, try and cancel it and wait in line

return;

} else {

// Clear out the current snackbar

mCurrentSnackbar = null;

// Otherwise, just show it now

showNextSnackbarLocked();

}

}

}

public void dismiss(Callback callback, int event) {

synchronized (mLock) {

if (isCurrentSnackbarLocked(callback)) {

cancelSnackbarLocked(mCurrentSnackbar, event);

} else if (isNextSnackbarLocked(callback)) {

cancelSnackbarLocked(mNextSnackbar, event);

}

}

}

public void onDismissed(Callback callback) {

synchronized (mLock) {

if (isCurrentSnackbarLocked(callback)) {

// If the callback is from a Snackbar currently show, remove it and show a new one

mCurrentSnackbar = null;

if (mNextSnackbar != null) {

showNextSnackbarLocked();

}

}

}

}

public void onShown(Callback callback) {

synchronized (mLock) {

if (isCurrentSnackbarLocked(callback)) {

scheduleTimeoutLocked(mCurrentSnackbar);

}

}

}

public void pauseTimeout(Callback callback) {

synchronized (mLock) {

if (isCurrentSnackbarLocked(callback) && !mCurrentSnackbar.paused) {

mCurrentSnackbar.paused = true;

mHandler.removeCallbacksAndMessages(mCurrentSnackbar);

}

}

}

public void restoreTimeoutIfPaused(Callback callback) {

synchronized (mLock) {

if (isCurrentSnackbarLocked(callback) && mCurrentSnackbar.paused) {

mCurrentSnackbar.paused = false;

scheduleTimeoutLocked(mCurrentSnackbar);

}

}

}

public boolean isCurrent(Callback callback) {

synchronized (mLock) {

return isCurrentSnackbarLocked(callback);

}

}

public boolean isCurrentOrNext(Callback callback) {

synchronized (mLock) {

return isCurrentSnackbarLocked(callback) || isNextSnackbarLocked(callback);

}

}

private static class SnackbarRecord {

final WeakReference callback;

int duration;

boolean paused;

SnackbarRecord(int duration, Callback callback) {

this.callback = new WeakReference<>(callback);

this.duration = duration;

}

boolean isSnackbar(Callback callback) {

return callback != null && this.callback.get() == callback;

}

}

private void showNextSnackbarLocked() {

if (mNextSnackbar != null) {

mCurrentSnackbar = mNextSnackbar;

mNextSnackbar = null

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

;

final Callback callback = mCurrentSnackbar.callback.get();

if (callback != null) {

callback.show();

} else {

// The callback doesn’t exist any more, clear out the Snackbar

mCurrentSnackbar = null;

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/658915.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号