This example is to learn a custom ViewGroup learning example, summary of the knowledge of custom ViewGroup.

To prepare

First, understand what it takes to customize a smooth-sliding ViewGroup.

  1. basis

    • To customize the ViewGroup onMeasure, the sub-view is required to measure the onMeasure and obtain its own size based on the data measured by the sub-view.

    • Custom ViewGoup onLayout processing, how to place the child View as long as careful calculation is very simple, remember to handle the margin of the child View and its own padding.

    • Event distribution mechanism, onInterceptTouchEvent intercepts sliding events and handles sliding related transactions.

      Actually handle the swipe in onTouchEvent.

  2. Use of several helper classes

    • Scroller users handle fast sliding and remember that the faster you slide, the faster the View rolls, mainly through the use of several corresponding apis.
    • VelocityTracker detects the sliding speed of the user’s gestures used to giveScrollerUse, remember to initialize first, then add gestures, and then calculate before fetching when needed.

Start by processing onMeasure/onLayout

Processing onMeasure is actually related to the layout of the ViewGroup. If the size of the ViewGroup is not accurate, wrAP_content, then the processing should take into account line breaks or column changes when the x/ Y axis space is insufficient. Row feed and column feed affects the final size of the ViewGroup.

What is implemented here is a ViewGroup with a streaming layout that can slide vertically.

PS: The measurement here does not take into account the layout factor, please look at the officer palm eye!

onMeasure

The overall steps are

  • Get its own measurement mode and the size data suggested by the parent View.
  • Loop to get the child View, callmeasureChildMethod makes the subview measure itself.
  • Calculate the ViewGroup height based on the layout policy.
  • Determine the final height according to the measurement mode
		@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // Get your own measurement mode and size data
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int realWidth = 0;
        int realHeight = getPaddingTop() + getPaddingBottom();

        // Measure the subview according to the subview and its own measurement mode
        int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            // Measure the subview
            measureChild(child, widthMeasureSpec, heightMeasureSpec);

            // Get the width and height of the child View
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            // Calculate its own height according to the data of the child View
            realHeight = realHeight + childHeight;
        }
        // The height is final
        switch (heightMode) {
            // If the exact value is set
            case MeasureSpec.EXACTLY:
                realHeight = heightSize;
                break;
            // If the contents of the package
            case MeasureSpec.AT_MOST:
                if (realHeight > heightSize) {
                    realHeight = heightSize;
                }
                break;
        }
        // The width is final
        switch (widthMode) {
            case MeasureSpec.AT_MOST:
                realWidth = widthSize;
                break;
            case MeasureSpec.EXACTLY:
                realWidth = widthSize;
                break;
        }
        // Set the final width and height
        setMeasuredDimension(realWidth, realHeight);
    }
Copy the code

onLayout

Layout is simple, give the child View whatever positioning parameters you want it to look like, calculate accurately, and pay attention to the padding and margin.

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int width = getMeasuredWidth();
        // The height that has been used
        viewHeight = getMeasuredHeight();
        // Top position at the beginning of the next line
        int nextTopPoint = getPaddingTop();

        // The width that has been used
        int useWidth = getPaddingLeft();
        // The height that has been used
        int useHeight = getPaddingTop();

        int childCount = getChildCount();

        MarginLayoutParams marginLayoutParams;
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);

            Log.e("sun", child.getLayoutParams().getClass().getName());
            marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();

            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            int left = useWidth + marginLayoutParams.leftMargin;
            int top = nextTopPoint + marginLayoutParams.topMargin;
            int right = left + childWidth + marginLayoutParams.rightMargin;
            int bottom = top + childHeight + marginLayoutParams.bottomMargin;

            // Whether a line break is required
            if(right > width) { left = getPaddingLeft() + marginLayoutParams.leftMargin; top = top + childHeight + marginLayoutParams.topMargin; right = left + childWidth + marginLayoutParams.rightMargin; bottom = top + childHeight + marginLayoutParams.bottomMargin; nextTopPoint = top; } useWidth = right; viewContentHeight = nextTopPoint; child.layout(left, top, right, bottom); }}Copy the code

Deal with sliding

All Views in Android support sliding, and the system provides scrollBy() and scrollTo methods to handle sliding. The difference between the two methods is that one is the position relative to the View, and the other is the absolute top of the View.

Intercept slide event

Intercepting sliding events is handled in onInterceptTouchEvent. Why? Easy to handle sliding conflicts, as well as avoid quilt View snatching events.

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int eventKey = ev.getAction();
        // Determine what the event is
        switch (eventKey) {
            case MotionEvent.ACTION_DOWN:
                // Write down the screen coordinates pressed
                if (mVelocityTracker == null) {
                    mVelocityTracker = VelocityTracker.obtain();
                }
                // If the scroller animation does not stop but the user has touched it, it should stop immediately
                if(! mScroller.isFinished()) { mScroller.abortAnimation(); } lastY = ev.getY();break;
            case MotionEvent.ACTION_MOVE:
                float moveY = ev.getY();
                float moveSlop = Math.abs(moveY - lastY);
            		// If the slider value is greater than the minimum slider value, the event is intercepted
                if (moveSlop > mTouchSlop) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
Copy the code

Handling sliding events

		@Override
    public boolean onTouchEvent(MotionEvent event) {
        int eventKey = event.getAction();
        switch (eventKey) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);

                float moveY = event.getY();
                int moveDis = (int) (lastY - moveY);
                // The value of x to slide, y to slide, x to slide, y to slide,
                // The range of x sliding, the range of Y sliding, the maximum value when x displays the edge effect (to be verified, possibly the rebound gap), the maximum value when y displays the edge effect (to be verified, possibly the rebound gap), whether to continue processing the subsequent events after performing the scroll (parameters that are not used)
                overScrollBy(0, moveDis, 0, getScrollY(), 0, getRangY(), 0.0.true);
                // Record the last coordinates
                lastY = event.getY();
                return true;
            case MotionEvent.ACTION_UP:
                // When fast sliding, use scroller to complete flow sliding
                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                float velocity = mVelocityTracker.getYVelocity();
                Log.e("sun"."Speed" + velocity + "Default speed" + mMinimumVelocity);

                if (Math.abs(velocity) > mMinimumVelocity) {
                    mScroller.fling(0, getScrollY(), 0, (int) -velocity, 0.0.0, getRangY());
// invalidate();
                }
                mVelocityTracker.clear();
                break;
        }
        return super.onTouchEvent(event);
    }
Copy the code

The full code is at gitHub