Everyone, can you give me a star for my project or my previous article? It’s so bitter. Github.com Nuggets article

Interviewer: So let’s continue to discuss this question. How do you monitor Scrollview and NestScrollView?

Me:?? There goes the black man again.

To analyze problems

Again, as in the last article, let’s look at what problems we need to solve.

  1. ScrollView NestScrollView slide monitor how to do.
  2. View has the attach and detch methods like RecyclerView, and the exposure time exceeds 1.5s.
  3. View appears half way.

Slide monitoring

The average person would probably tell you, well, you define a scrollView, and then on onScrollChanged implement a slider listening callback or something. I’m sorry, BUT I can’t show you another magic trick.

Let’s start with a few of the interfaces included in the ViewTreeObserver.

Inner class interface note
ViewTreeObserver.OnPreDrawListener Interface that is called when the view tree is about to be drawn
ViewTreeObserver.OnGlobalLayoutListener Interface that is invoked when the layout of the View tree changes or when the View changes its visible state in the View tree
ViewTreeObserver.OnGlobalFocusChangeListener Interface that is invoked when the focus state of a view tree changes
ViewTreeObserver.OnScrollChangedListener Interface that is called when some component of the view tree scrolls
ViewTreeObserver.OnTouchModeChangeListener The interface cell that is called when the touch mode of the view tree changes

Did you guys find anything strange mixed in there? Ha ha ha.

Conventional analysis of the source code

Theoretically, all view states and so on are viewwrotimp dependent. ViewTreeObserver in particular, so our source code analysis also starts with ViewRootImp.

class ViewRootImp {
    // Draw the root view
    private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        if(! surface.isValid()) {return false;
        }

        if (DEBUG_FPS) {
            trackFPS();
        }

        if(! sFirstDrawComplete) {synchronized (sFirstDrawHandlers) {
                sFirstDrawComplete = true;
                final int count = sFirstDrawHandlers.size();
                for (int i = 0; i< count; i++) {
                    mHandler.post(sFirstDrawHandlers.get(i));
                }
            }
        }

        scrollToRectOrFocus(null.false);

        if (mAttachInfo.mViewScrollChanged) {
            mAttachInfo.mViewScrollChanged = false;
            // Call the slide listener of viewTreemAttachInfo.mTreeObserver.dispatchOnScrollChanged(); }...returnuseAsyncReport; }}Copy the code

The above code, you can see that when mAttachInfo. MViewScrollChanged state of the bit is set to true, will inform viewTree call slide monitoring. So the entry point is very simple. When someone sets this value to true, it will trigger the slide listener.

class View {
     final static class AttachInfo {
         /** * Set to true if a view has been scrolled. */
        @UnsupportedAppUsage
        boolean mViewScrollChanged;
     }
     /**
     * This is called in response to an internal scroll in this view (i.e., the
     * view scrolled its own contents). This is typically as a result of
     * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
     * called.
     *
     * @param l Current horizontal scroll origin.
     * @param t Current vertical scroll origin.
     * @param oldl Previous horizontal scroll origin.
     * @param oldt Previous vertical scroll origin.
     */
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        notifySubtreeAccessibilityStateChangedIfNeeded();

        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt);
        }

        mBackgroundSizeChanged = true;
        mDefaultFocusHighlightSizeChanged = true;
        if(mForegroundInfo ! =null) {
            mForegroundInfo.mBoundsChanged = true;
        }

        final AttachInfo ai = mAttachInfo;
        if(ai ! =null) {
            ai.mViewScrollChanged = true;
        }

        if(mListenerInfo ! =null&& mListenerInfo.mOnScrollChangeListener ! =null) {
            mListenerInfo.mOnScrollChangeListener.onScrollChange(this, l, t, oldl, oldt); }}}Copy the code

AttachInfo is the inner class of the View, and as described in the comment, set this value to true when the View slides. OnScrollChanged is also a protected method of the View. This method is called when the sliding state of the ScrollView and NestScrollView is changed, and inside this method the state is set to true.

The test results

OnScrollChangedListener triggers a callback for both ScrollView and NestScrollView slides. The above code analysis shows that when two sliding components slide, the corresponding callback listener will be triggered.

View appears halfway

This monitoring method is still the same as the previous article. Please read the previous article directly.

1.5s exposure time

So let’s go back to the previous article and say onAttachedToWindow and onDetachedFromWindow, are these two methods available? The answer is definitely no. So what should we do?

No guns, no guns. Build your own.

interface ExposeViewAdapter {
    fun setExposeListener(listener: (Float) -> Unit)

    fun setExposeListener(listener: OnExposeListener)

    fun onVisibleChange(isCover: Boolean)
}
Copy the code

Instead of onAttachedToWindow onDetachedFromWindow, we can provide an adapter that provides onVisibleChange.


class ExposeScrollChangeListener(scrollView: ViewGroup) :
    ViewTreeObserver.OnScrollChangedListener, ViewTreeObserver.OnGlobalLayoutListener {

    private val rootView: ViewGroup? = scrollView.getChildAt(0) as ViewGroup?
    private val views = hashSetOf<View>()
    private var lastChildCount = 0

    init {

    }

    override fun onScrollChanged(a) {
        views.forEach {
            val exposeView = it as ExposeViewAdapter
            exposeView.onVisibleChange(it.visibleRect())
        }
    }

    private fun checkViewSize(a){ rootView? .apply { lastChildCount = childCount getChildExpose(rootView) } }private fun getChildExpose(view: View?).{ view? .let {if (it is ExposeViewAdapter) {
                views.add(it)
            }
            if (view is ViewGroup) {
                // Traversing the ViewGroup is a recursive call to the subview plus 1
                for (i in 0 until view.childCount) {
                    val child = view.getChildAt(i)
                    if (child is ExposeViewAdapter) {
                        views.add(child)
                    }
                    if (child is ViewGroup) {
                        getChildExpose(child)
                    }
                }
            }
        }
    }

    override fun onGlobalLayout(a) {
        val timeUsage = System.currentTimeMillis()
        checkViewSize()
        Log.i("expose"."timeCoast:${System.currentTimeMillis() - timeUsage}")}}Copy the code

First we need to monitor the onGlobalLayout method to scan the current ViewTree and retrieve all views that implement the ExposeViewAdapter in case this method fires. When the slide listener is triggered, call the previous view occlusion method to determine whether the current view appears on the view, and then call onVisibleChange to notify whether the view has been removed from the window.

The last

Interviewer: Good for you.

Me: modest rational small dish force.

Interviewer: It still doesn’t feel smart if you use dynamic staking.

Me: Excuse me, 2nd Battalion commander, bring up my Italian cannon.

Interviewer: Go home and wait for the announcement.