/* * Copyright 2015 Hippo Seven * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hippo.widget; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import com.hippo.nimingban.R; import java.util.ArrayList; import java.util.List; /** * A ViewGroup that can layout views in line and auto wrap * * @author Hippo * */ public class AutoWrapLayout extends ViewGroup { private final List<Rect> rectList = new ArrayList<>(); private Alignment mAlignment; private static final Alignment[] sBaseLineArray = { Alignment.TOP, Alignment.CENTER, Alignment.BOTTOM }; public enum Alignment { TOP(0), CENTER(1), BOTTOM(2); Alignment(int ni) { nativeInt = ni; } final int nativeInt; } public AutoWrapLayout(Context context) { super(context); } public AutoWrapLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AutoWrapLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoWrapLayout, defStyle, 0); int index = a.getInt(R.styleable.AutoWrapLayout_alignment, -1); if (index >= 0) { setAlignment(sBaseLineArray[index]); } a.recycle(); } public void setAlignment(Alignment baseLine) { if (baseLine == null) { return; } if (mAlignment != baseLine) { mAlignment = baseLine; requestLayout(); invalidate(); } } public Alignment getAlignment() { return mAlignment; } private void adjustBaseLine(int lineHeight, int startIndex, int endIndex) { if (mAlignment == Alignment.TOP) return; for (int index = startIndex; index < endIndex; index++) { final View child = getChildAt(index); final LayoutParams lp = (LayoutParams)child.getLayoutParams(); Rect rect = rectList.get(index); int offsetRaw = lineHeight - rect.height() - lp.topMargin - lp.bottomMargin; if (mAlignment == Alignment.CENTER) rect.offset(0, offsetRaw/2); else if (mAlignment == Alignment.BOTTOM) rect.offset(0, offsetRaw); } } // TODO Take vertical mode /** * each row or line at least show one child * * horizontal only show child can show or partly show in parent */ @SuppressLint("DrawAllocation") @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int maxWidth = MeasureSpec.getSize(widthMeasureSpec); int maxHeight = MeasureSpec.getSize(heightMeasureSpec); if (widthMode == MeasureSpec.UNSPECIFIED) maxWidth = Integer.MAX_VALUE; if (heightMode == MeasureSpec.UNSPECIFIED) maxHeight = Integer.MAX_VALUE; int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int paddingRight = getPaddingRight(); int paddingBottom = getPaddingBottom(); int maxRightBound = maxWidth - paddingRight; int maxBottomBound = maxHeight - paddingBottom; int left; int top; int right; int bottom; int rightBound = paddingLeft; int maxRightNoPadding = rightBound; int bottomBound; int lastMaxBottom = paddingTop; int maxBottom = lastMaxBottom; int childWidth; int childHeight; int lineStartIndex = 0; int lineEndIndex; // endIndex + 1 rectList.clear(); int childCount = getChildCount(); for (int index = 0; index < childCount; index++) { final View child = getChildAt(index); child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); if (child.getVisibility() == View.GONE) continue; final LayoutParams lp = (LayoutParams)child.getLayoutParams(); childWidth = child.getMeasuredWidth(); childHeight = child.getMeasuredHeight(); left = rightBound + lp.leftMargin; right = left + childWidth; rightBound = right + lp.rightMargin; if (rightBound > maxRightBound) { // Go to next row lineEndIndex = index; // Adjust child position base on baseline adjustBaseLine(maxBottom - lastMaxBottom, lineStartIndex, lineEndIndex); // If child can't show in parent begin this line if (maxBottom >= maxBottomBound) break; // If it is first item in line, try to show it all if (lineEndIndex == lineStartIndex) { child.measure(MeasureSpec.makeMeasureSpec( maxWidth - paddingLeft - paddingRight - lp.leftMargin - lp.rightMargin, MeasureSpec.AT_MOST), MeasureSpec.UNSPECIFIED); childWidth = child.getMeasuredWidth(); childHeight = child.getMeasuredHeight(); } left = paddingLeft + lp.leftMargin; right = left + childWidth; rightBound = right + lp.rightMargin; lastMaxBottom = maxBottom; top = lastMaxBottom + lp.topMargin; bottom = top + childHeight; bottomBound = bottom + lp.bottomMargin; lineStartIndex = index; } else { top = lastMaxBottom + lp.topMargin; bottom = top + childHeight; bottomBound = bottom + lp.bottomMargin; } // Update max if (rightBound > maxRightNoPadding) maxRightNoPadding = rightBound; if (bottomBound > maxBottom) maxBottom = bottomBound; Rect rect = new Rect(); rect.left = left; rect.top = top; rect.right = right; rect.bottom = bottom; rectList.add(rect); } // Handle last line baseline adjustBaseLine(maxBottom - lastMaxBottom, lineStartIndex, rectList.size()); int measuredWidth; int measuredHeight; if (widthMode == MeasureSpec.EXACTLY) measuredWidth = maxWidth; else measuredWidth = maxRightNoPadding + paddingRight; if (heightMode == MeasureSpec.EXACTLY) measuredHeight = maxHeight; else { measuredHeight = maxBottom + paddingBottom; if (heightMode == MeasureSpec.AT_MOST) measuredHeight = measuredHeight > maxHeight ? maxHeight : measuredHeight; } setMeasuredDimension(measuredWidth, measuredHeight); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = rectList.size(); for(int i = 0; i < count; i++){ final View child = this.getChildAt(i); if (child.getVisibility() == View.GONE) continue; Rect rect = rectList.get(i); child.layout(rect.left, rect.top, rect.right, rect.bottom); } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } public static class LayoutParams extends MarginLayoutParams { public LayoutParams() { this(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } public LayoutParams(MarginLayoutParams source) { super(source); } } }