Snackbar 源码理解学习


//Snackbar 源码分析:
//  

public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
            @Duration int duration) {
			//找到一个合适的parent,循环遍历父view 直到取到合适的view
        final ViewGroup parent = findSuitableParent(view);
        if (parent == null) {
            throw new IllegalArgumentException("No suitable parent found from the given view. "
                    + "Please provide a valid view.");
        }
		//如果传进来的view为null 抛出异常
		
		// 

        final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
		//R.layout.design_layout_snackbar_include 应该为Snackbar 的布局
		//将 Snackbar布局初始化关联到 上面找到的一个合适的parent 
		// LayoutInflater.inflate 三个参数介绍的博客:https://blog.csdn.net/u012702547/article/details/52628453
		// inflate 三个参数说明 第一个要加载的xml 布局
		// 第二个参数  要依赖的父view 
		// 是否添加到父View 中 如果为true 那么这个方法返回的是parent 的布局并
		//且将需要加载的布局添加到parent中,如果为false 返回的则是 需要加载布局的view
		// 
		
		// 不明白这种填充布局的方式,为什么能直接转换为自定义的SnackbarContentLayout
		// 而SnackbarContentLayout类文件中并未发现有关联到  R.layout.design_layout_snackbar_include 布局的代码 ??? 
		
        final SnackbarContentLayout content =
                (SnackbarContentLayout) inflater.inflate(
                        R.layout.design_layout_snackbar_include, parent, false);
						//创建Snackbar 
        final Snackbar snackbar = new Snackbar(parent, content, content);
	
        snackbar.setText(text); //设置Snackbar文字
        snackbar.setDuration(duration); //设置Snackbar时长
        return snackbar;
    }

	
	
	//找到合适的父View 
	 private static ViewGroup findSuitableParent(View view) {
        ViewGroup fallback = null;
        do {
            if (view instanceof CoordinatorLayout) {
			//如果此View 是 CoordinatorLayout 转换为ViewGroup返回
                // We've found a CoordinatorLayout, use it
                return (ViewGroup) view;
            } else if (view instanceof FrameLayout) { // 如果传入的view是 FrameLayout
                if (view.getId() == android.R.id.content) {
				//如果当前是 点击的是 DecorView 中的ContentView 转换为ViewGroup返回
                    // If we've hit the decor content view, then we didn't find a CoL in the
                    // hierarchy, so use it.
                    return (ViewGroup) view;
                } else {
                    // It's not the content view but we'll use it as our fallback
                    fallback = (ViewGroup) view;  // 直接转换为ViewGroup返回
                }
            }

            if (view != null) {
			
			//如果当前view不为null ,获取父view 判断父view 是否是View如果是 则进行下一次循环
			//如果 不是View 退出循环 直接返回null
			
                // Else, we will loop and crawl up the view hierarchy and try to find a parent
                final ViewParent parent = view.getParent();
                view = parent instanceof View ? (View) parent : null;
            }
        } while (view != null);

        // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback
        return fallback;
    }
	
	//Snackbar snackbar = new Snackbar(parent, content, content); Snackbar的构造方法
	
	//首先调用父类的构造方法
	private Snackbar(ViewGroup parent, View content, ContentViewCallback contentViewCallback) {
        super(parent, content, contentViewCallback);
    }
	
	//Snackbar 继承于BaseTransientBottomBar
	
	
	//父类的构造方法
	/**
     * Constructor for the transient bottom bar.
     *
     * @param parent The parent for this transient bottom bar.
     * @param content The content view for this transient bottom bar.
     * @param contentViewCallback The content view callback for this transient bottom bar.
     */
    protected BaseTransientBottomBar(@NonNull ViewGroup parent, @NonNull View content,
            @NonNull ContentViewCallback contentViewCallback) {
        if (parent == null) {
            throw new IllegalArgumentException("Transient bottom bar must have non-null parent");
        }
        if (content == null) {
            throw new IllegalArgumentException("Transient bottom bar must have non-null content");
        }
        if (contentViewCallback == null) {
            throw new IllegalArgumentException("Transient bottom bar must have non-null callback");
        }

		//将 获取到的父View parent 赋值给 mTargetParent,此时父View parent 并未包含 Snackbar 的View 
        mTargetParent = parent;
		
		//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);

        ViewCompat.setAccessibilityLiveRegion(mView,
                ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);
        ViewCompat.setImportantForAccessibility(mView,
                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);

        // Make sure that we fit system windows and have a listener to apply any insets
        ViewCompat.setFitsSystemWindows(mView, true);
        ViewCompat.setOnApplyWindowInsetsListener(mView,
                new android.support.v4.view.OnApplyWindowInsetsListener() {
                    @Override
                    public WindowInsetsCompat onApplyWindowInsets(View v,
                            WindowInsetsCompat insets) {
                        // Copy over the bottom inset as padding so that we're displayed
                        // above the navigation bar
                        v.setPadding(v.getPaddingLeft(), v.getPaddingTop(),
                                v.getPaddingRight(), insets.getSystemWindowInsetBottom());
                        return insets;
                    }
                });

        mAccessibilityManager = (AccessibilityManager)
                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
    }
	
	
	// SnackbarContentLayout 代码, 继承 LinearLayout
	
	public class SnackbarContentLayout extends LinearLayout implements
        BaseTransientBottomBar.ContentViewCallback {
    private TextView mMessageView;  // Snackbar 的文字
    private Button mActionView;   // Snackbar 的按钮

    private int mMaxWidth;
    private int mMaxInlineActionWidth;

    public SnackbarContentLayout(Context context) {
        this(context, null);
    }

    public SnackbarContentLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout);
        mMaxWidth = a.getDimensionPixelSize(R.styleable.SnackbarLayout_android_maxWidth, -1);
        mMaxInlineActionWidth = a.getDimensionPixelSize(
                R.styleable.SnackbarLayout_maxActionInlineWidth, -1);
        a.recycle();
    }

	
	// Finalize inflating a view from XML. This is called as the last phase of inflation, after all child views have //been added.
	// 
///Even if the subclass overrides onFinishInflate, they should always be sure to call the super method, so that we //get called.
	///即使子类覆盖了onFinishInflate,也应该总是调用super方法,这样我们就可以被调用了
	// xml 被inflate 结束之后调用的方法
	
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMessageView = findViewById(R.id.snackbar_text);
        mActionView = findViewById(R.id.snackbar_action);
    }

    public TextView getMessageView() {
        return mMessageView;
    }

    public Button getActionView() {
        return mActionView; 
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		//如果文字宽度大于设置的最大宽度 需要重新测量View 
        if (mMaxWidth > 0 && getMeasuredWidth() > mMaxWidth) {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

        final int multiLineVPadding = getResources().getDimensionPixelSize(
                R.dimen.design_snackbar_padding_vertical_2lines);
        final int singleLineVPadding = getResources().getDimensionPixelSize(
                R.dimen.design_snackbar_padding_vertical);
				
				//判断文本行数是否大于1行
        final boolean isMultiLine = mMessageView.getLayout().getLineCount() > 1;

        boolean remeasure = false;
        if (isMultiLine && mMaxInlineActionWidth > 0
                && mActionView.getMeasuredWidth() > mMaxInlineActionWidth) {
            if (updateViewsWithinLayout(VERTICAL, multiLineVPadding,
                    multiLineVPadding - singleLineVPadding)) {
                remeasure = true;
            }
        } else {
            final int messagePadding = isMultiLine ? multiLineVPadding : singleLineVPadding;
            if (updateViewsWithinLayout(HORIZONTAL, messagePadding, messagePadding)) {
                remeasure = true;
            }
        }

        if (remeasure) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    private boolean updateViewsWithinLayout(final int orientation,
            final int messagePadTop, final int messagePadBottom) {
        boolean changed = false;
        if (orientation != getOrientation()) {
            setOrientation(orientation);
            changed = true;
        }
        if (mMessageView.getPaddingTop() != messagePadTop
                || mMessageView.getPaddingBottom() != messagePadBottom) {
            updateTopBottomPadding(mMessageView, messagePadTop, messagePadBottom);
            changed = true;
        }
        return changed;
    }

    private static void updateTopBottomPadding(View view, int topPadding, int bottomPadding) {
        if (ViewCompat.isPaddingRelative(view)) {
            ViewCompat.setPaddingRelative(view,
                    ViewCompat.getPaddingStart(view), topPadding,
                    ViewCompat.getPaddingEnd(view), bottomPadding);
        } else {
            view.setPadding(view.getPaddingLeft(), topPadding,
                    view.getPaddingRight(), bottomPadding);
        }
    }

    @Override
    public void animateContentIn(int delay, int duration) {
        mMessageView.setAlpha(0f);
        mMessageView.animate().alpha(1f).setDuration(duration)
                .setStartDelay(delay).start();

        if (mActionView.getVisibility() == VISIBLE) {
            mActionView.setAlpha(0f);
            mActionView.animate().alpha(1f).setDuration(duration)
                    .setStartDelay(delay).start();
        }
    }

    @Override
    public void animateContentOut(int delay, int duration) {
        mMessageView.setAlpha(1f);
        mMessageView.animate().alpha(0f).setDuration(duration)
                .setStartDelay(delay).start();

        if (mActionView.getVisibility() == VISIBLE) {
            mActionView.setAlpha(1f);
            mActionView.animate().alpha(0f).setDuration(duration)
                    .setStartDelay(delay).start();
        }
    }
}

	
	
	
	
	
	


你可能感兴趣的:(Snackbar 源码理解学习)