The reason

In the wechat “chat message”, there is a “chat mini program”. Recently, on the GitHub homepage, your related status, such as Issues, has this effect at the top of the page, and when you click on it, the collapse effect opens.

When it comes to my own project, there was a list in the previous APP “One Two”, which also had a similar effect. However, time was tight at that time, so I used LinearLayout and margin to temporarily realize the version.

implementation

Think about the effect, in fact, should not be too difficult, just control layout. So, why don’t you masturbate? What if we need it again?

In fact, in addition to the layout problem, there is a problem to consider, is the order of draw. The default order must be from the first to the last, so that’s not how it folds. The ones drawn later will be on top of the ones drawn earlier. So the final result is the last one drawn on top.

Once the relevant questions are clear, just go ahead and masturbate. While onLayout() is the main one, the onMeasure() method also needs to be handled. The control width here is the total width of all the child views, of course this is the minimum, if you set match_parent then I can’t stop you. And if the exact value you give is less than the measured minimum width, the minimum width will be used in the end. I’ll just take the height that’s highest in the child view. This control uses a scenario in which all child views are, in principle, one size.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSize = getPaddingLeft() + getPaddingRight();
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int maxWidth = 0;
    if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
        maxWidth = 0;
    }
    if (widthMode == MeasureSpec.EXACTLY) {
        maxWidth = MeasureSpec.getSize(widthMeasureSpec);
    }

    int heightSize = getPaddingBottom() + getPaddingBottom();
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        final LayoutParams lp = child.getLayoutParams();
        int heightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
        int widthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
        child.measure(widthSpec, heightSpec);
        int height = child.getMeasuredHeight();
        heightSize = Math.max(heightSize, height);
        widthSize += child.getMeasuredWidth();
    }
    widthSize += extraSpace * (childCount - 1);
    heightSize += getPaddingBottom() + getPaddingTop();
    setMeasuredDimension(Math.max(widthSize, maxWidth), heightSize);
}
Copy the code

OnLayout () supports left-to-right expansion and right-to-left expansion. For example, if you want to use left-place, by default all child views are laid out from the left, the left is the paddingLeft of the ViewGroup, and extra is position*width*fraction. For example, the second one, position is 1, and the fraction is a ratio that can be set externally. Similarly, expanding is the sum of the widths of all views before the current view.

Wait, do you think something’s missing? Well, when the layout comes out, you will notice that there is no space between the sub-views. This will not work, so add an extraSpace field that specifies how much space is needed. But this extraSpace you don’t know how the user will pass it, so you need to limit it, otherwise it will directly break your calculation logic.

void layoutChildrenLeft(int left, int right) { final int count = getChildCount(); final int parentRight = getPaddingLeft(); int parentLeft = getPaddingLeft(); final int parentTop = getPaddingTop(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() == VISIBLE) { final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int dy = 0; if (i > 0) { if (fold) { dy = (int) (i * fraction * width * animatedFraction); } else { for (int j = 0; j <= i - 1; j++) { int measuredWidth = getChildAt(j).getMeasuredWidth(); dy += measuredWidth; } dy *= animatedFraction; } if (extraSpace < 0 && -i * extraSpace > dy) { dy = 0; } else { dy += i * extraSpace; } } child.layout(parentLeft + dy, parentTop, parentLeft + dy + width, parentTop + height); }}}Copy the code

At this point, the default and expanded layout is basically done, but this is certainly not enough, ah, to add special effects ah, to have a switch effect ah. So you’ll have to write a layoutChangeAnimator and a TimeInterpolator, plus a toggle() method to switch expansion and default state.

It’s almost perfect, and then suddenly you realize that there should be a maxCount field, and when you add more than this threshold, it collapses automatically, and if it doesn’t, it expands by default.

Finally, there is the issue of draw order mentioned above, in fact, this is also very simple, ViewGroup has provided us with functional support.

@Override
protected int getChildDrawingOrder(int childCount, int i) {
    return childCount - 1 - i;
}
Copy the code

Method names that makes clear, this method is used to return the order, change here for drawing order, but this method is not effective by default, if you want to use it, then you have to specify that it is available, there is another method, setChildrenDrawingOrderEnabled (true); .

use

implementation 'com.lovejjfg.blinds:blinds:lastedversion'

XML:

    <com.lovejjfg.blinds.BlindsLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/blinds"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:blindsFold="true"
        app:blindsFraction="60%"
        app:blindsMaxCount="4">
Copy the code

code:

Blinds4. Orientation = RIGHT blinds4. MaxCount = 4 blinds4 blinds4.setAnimationDuration(1000) blinds4.setInterpolator(BounceInterpolator())Copy the code

To this, the basic introduction, source making: https://github.com/lovejjfg/Blinds