Improve GLView

This commit is contained in:
seven332 committed 2015-07-26 11:51:42 +08:00
1 parent ad081dd962
commit 8bfffcda1f
2 files changed
+534 -22

No files matched your search

@@ -279,6 +279,8 @@ public class GLRootView extends GLSurfaceView
}
Log.i(TAG, "layout content pane " + w + "x" + h + " (compensation " + mCompensation + ")");
if (mContentView != null && w != 0 && h != 0) {
mContentView.measure(GLView.MeasureSpec.makeMeasureSpec(w, GLView.MeasureSpec.EXACTLY),
GLView.MeasureSpec.makeMeasureSpec(h, GLView.MeasureSpec.EXACTLY));
mContentView.layout(0, 0, w, h);
}
// Uncomment this to dump the view hierarchy.
@@ -631,6 +633,11 @@ public class GLRootView extends GLSurfaceView
protected void onDetachedFromWindow() {
unfreeze();
super.onDetachedFromWindow();
// TODO Is it right ?
if (mContentView != null) {
mContentView.detachFromRoot();
}
}
@Override
@@ -16,6 +16,7 @@
package com.hippo.ehviewer.gallery.ui;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.Log;
@@ -24,6 +25,7 @@ import android.view.MotionEvent;
import com.hippo.ehviewer.gallery.anim.CanvasAnimation;
import com.hippo.ehviewer.gallery.anim.StateTransitionAnimation;
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
import com.hippo.ehviewer.gallery.glrenderer.GLPaint;
import com.hippo.yorozuya.AssertUtils;
import java.util.ArrayList;
@@ -46,6 +48,8 @@ import java.util.ArrayList;
public class GLView {
private static final String TAG = "GLView";
private static final boolean DEBUG_DRAW_BOUNDS = false;
public static final int VISIBLE = 0;
public static final int INVISIBLE = 1;
@@ -57,7 +61,16 @@ public class GLView {
void onClick(GLView v);
}
protected final static GLPaint mDrawBoundsPaint;
static {
mDrawBoundsPaint = new GLPaint();
mDrawBoundsPaint.setColor(Color.RED);
mDrawBoundsPaint.setLineWidth(2);
}
protected final Rect mBounds = new Rect();
private final Rect mRenderCheckBounds = new Rect();
protected final Rect mPaddings = new Rect();
private GLRoot mRoot;
@@ -80,6 +93,20 @@ public class GLView {
protected int mScrollHeight = 0;
protected int mScrollWidth = 0;
/**
* The minimum height of the view. We'll try our best to have the height
* of this view to at least this amount.
*/
private int mMinHeight;
/**
* The minimum width of the view. We'll try our best to have the width
* of this view to at least this amount.
*/
private int mMinWidth;
private LayoutParams mLayoutParams;
private float [] mBackgroundColor;
private StateTransitionAnimation mTransition;
@@ -118,6 +145,7 @@ public class GLView {
onAttachToRoot(root);
}
// TODO It is not called when the
// This should only be called on the content pane (the topmost GLView).
public void detachFromRoot() {
AssertUtils.assertTrue(mParent == null && mRoot != null);
@@ -139,15 +167,46 @@ public class GLView {
// Adds a child to this GLView.
public void addComponent(GLView component) {
addComponent(component, -1, null);
}
public void addComponent(GLView component, int index) {
addComponent(component, index, null);
}
public void addComponent(GLView component, LayoutParams params) {
addComponent(component, -1, params);
}
public void addComponent(GLView component, int index, LayoutParams params) {
// Make component is not null
if (component == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// Make sure the component doesn't have a parent currently.
if (component.mParent != null) throw new IllegalStateException();
if (component.mParent != null) {
throw new IllegalStateException(
"Component " + this + " being added, but it already has a parent");
}
// Ensure index is valid
if (index < 0) {
index = getComponentCount();
}
// Ensure params is valid
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
// Build parent-child links
if (mComponents == null) {
mComponents = new ArrayList<GLView>();
mComponents = new ArrayList<>();
}
mComponents.add(component);
mComponents.add(index, component);
component.mParent = this;
component.setLayoutParams(params);
// If this is added after we have a root, tell the component.
if (mRoot != null) {
@@ -165,6 +224,19 @@ public class GLView {
return false;
}
public boolean removeComponentAt(int index) {
if (mComponents == null) {
return false;
}
if (index >= 0 && index < getComponentCount()) {
GLView component = mComponents.remove(index);
removeOneComponent(component);
return true;
} else {
return false;
}
}
// Removes all children of this GLView.
public void removeAllComponents() {
for (int i = 0, n = mComponents.size(); i < n; ++i) {
@@ -197,6 +269,30 @@ public class GLView {
return mBounds.bottom - mBounds.top;
}
/**
* Offset this GLView's vertical location by the specified number of pixels.
*
* @param offset the number of pixels to offset the view by
*/
public void offsetTopAndBottom(int offset) {
if (offset != 0){
mBounds.offset(0, offset);
invalidate();
}
}
/**
* Offset this GLView's horizontal location by the specified amount of pixels.
*
* @param offset the number of pixels to offset the view by
*/
public void offsetLeftAndRight(int offset) {
if (offset != 0){
mBounds.offset(offset, 0);
invalidate();
}
}
public GLRoot getGLRoot() {
return mRoot;
}
@@ -211,8 +307,8 @@ public class GLView {
// Request re-layout of the view hierarchy.
public void requestLayout() {
mViewFlags |= FLAG_LAYOUT_REQUESTED;
mLastHeightSpec = -1;
mLastWidthSpec = -1;
mLastHeightSpec = -1;
if (mParent != null) {
mParent.requestLayout();
} else {
@@ -229,19 +325,31 @@ public class GLView {
transitionActive = mTransition.isActive();
}
renderBackground(canvas);
onRender(canvas);
canvas.save();
if (transitionActive) {
mTransition.applyContentTransform(this, canvas);
}
for (int i = 0, n = getComponentCount(); i < n; ++i) {
renderChild(canvas, getComponent(i));
GLView component = getComponent(i);
if (Rect.intersects(mRenderCheckBounds, component.mBounds)) {
renderChild(canvas, getComponent(i));
}
}
canvas.restore();
if (DEBUG_DRAW_BOUNDS) {
canvas.drawRect(0, 0, getWidth(), getHeight(), mDrawBoundsPaint);
}
if (transitionActive) {
mTransition.applyOverlay(this, canvas);
}
}
protected void onRender(GLCanvas canvas) {
}
public void setIntroAnimation(StateTransitionAnimation intro) {
mTransition = intro;
if (mTransition != null) mTransition.start();
@@ -347,39 +455,97 @@ public class GLView {
public void layout(int left, int top, int right, int bottom) {
boolean sizeChanged = setBounds(left, top, right, bottom);
final boolean forceLayout = (mViewFlags & FLAG_LAYOUT_REQUESTED) == FLAG_LAYOUT_REQUESTED;
if (sizeChanged || forceLayout) {
onLayout(sizeChanged, left, top, right, bottom);
}
mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
// We call onLayout no matter sizeChanged is true or not because the
// orientation may change without changing the size of the View (for
// example, rotate the device by 180 degrees), and we want to handle
// orientation change in onLayout.
onLayout(sizeChanged, left, top, right, bottom);
}
private boolean setBounds(int left, int top, int right, int bottom) {
boolean sizeChanged = (right - left) != (mBounds.right - mBounds.left)
|| (bottom - top) != (mBounds.bottom - mBounds.top);
mBounds.set(left, top, right, bottom);
mRenderCheckBounds.set(0, 0, right - left, bottom - top);
return sizeChanged;
}
public void measure(int widthSpec, int heightSpec) {
if (widthSpec == mLastWidthSpec && heightSpec == mLastHeightSpec
&& (mViewFlags & FLAG_LAYOUT_REQUESTED) == 0) {
return;
}
final boolean forceLayout = (mViewFlags & FLAG_LAYOUT_REQUESTED) == FLAG_LAYOUT_REQUESTED;
final boolean isExactly = MeasureSpec.getMode(widthSpec) == MeasureSpec.EXACTLY &&
MeasureSpec.getMode(heightSpec) == MeasureSpec.EXACTLY;
final boolean matchingSize = isExactly &&
getMeasuredWidth() == MeasureSpec.getSize(widthSpec) &&
getMeasuredHeight() == MeasureSpec.getSize(heightSpec);
if (forceLayout || !matchingSize &&
(widthSpec != mLastWidthSpec ||
heightSpec != mLastHeightSpec)) {
mLastWidthSpec = widthSpec;
mLastHeightSpec = heightSpec;
mLastWidthSpec = widthSpec;
mLastHeightSpec = heightSpec;
mViewFlags &= ~FLAG_SET_MEASURED_SIZE;
onMeasure(widthSpec, heightSpec);
if ((mViewFlags & FLAG_SET_MEASURED_SIZE) == 0) {
throw new IllegalStateException(getClass().getName()
+ " should call setMeasuredSize() in onMeasure()");
mViewFlags &= ~FLAG_SET_MEASURED_SIZE;
onMeasure(widthSpec, heightSpec);
if ((mViewFlags & FLAG_SET_MEASURED_SIZE) == 0) {
throw new IllegalStateException(getClass().getName()
+ " should call setMeasuredSize() in onMeasure()");
}
}
}
protected void onMeasure(int widthSpec, int heightSpec) {
setMeasuredSize(getDefaultSize(getMinimumWidth(), widthSpec),
getDefaultSize(getMinimumHeight(), heightSpec));
}
/**
* Returns the minimum height of the view.
*
* @return the minimum height the view will try to be.
*
* @see #setMinimumHeight(int)
*/
public int getMinimumHeight() {
return mMinHeight;
}
/**
* Sets the minimum height of the view. It is not guaranteed the view will
* be able to achieve this minimum height (for example, if its parent layout
* constrains it with less available height).
*
* @param minHeight The minimum height the view will try to be.
*
* @see #getMinimumHeight()
*/
public void setMinimumHeight(int minHeight) {
mMinHeight = minHeight;
requestLayout();
}
/**
* Returns the minimum width of the view.
*
* @return the minimum width the view will try to be.
*
* @see #setMinimumWidth(int)
*/
public int getMinimumWidth() {
return mMinWidth;
}
/**
* Sets the minimum width of the view. It is not guaranteed the view will
* be able to achieve this minimum width (for example, if its parent layout
* constrains it with less available width).
*
* @param minWidth The minimum width the view will try to be.
*
* @see #getMinimumWidth()
*/
public void setMinimumWidth(int minWidth) {
mMinWidth = minWidth;
requestLayout();
}
protected void setMeasuredSize(int width, int height) {
@@ -400,6 +566,143 @@ public class GLView {
boolean changeSize, int left, int top, int right, int bottom) {
}
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.AT_MOST:
return size == 0 ? specSize : Math.min(size, specSize);
}
return result;
}
public static int getComponentSpec(int spec, int childSize) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childSize >= 0) {
resultSize = childSize;
resultMode = MeasureSpec.EXACTLY;
} else if (childSize == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childSize == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childSize >= 0) {
// Child wants a specific size... so be it
resultSize = childSize;
resultMode = MeasureSpec.EXACTLY;
} else if (childSize == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childSize == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childSize >= 0) {
// Child wants a specific size... let him have it
resultSize = childSize;
resultMode = MeasureSpec.EXACTLY;
} else if (childSize == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childSize == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
public static void measureComponent(GLView component, int widthSpec, int heightSpec) {
final LayoutParams lp = component.getLayoutParams();
final int componentWidthSpec = getComponentSpec(widthSpec, lp.width);
final int componentHeightSpec = getComponentSpec(heightSpec, lp.height);
component.measure(componentWidthSpec, componentHeightSpec);
}
public static void measureAllComponents(GLView parent, int widthSpec, int heightSpec) {
for (int i = 0, n = parent.getComponentCount(); i < n; i++) {
GLView component = parent.getComponent(i);
measureComponent(component, widthSpec, heightSpec);
}
}
public LayoutParams getLayoutParams() {
return mLayoutParams;
}
public void setLayoutParams(LayoutParams params) {
if (params == null) {
if (mParent == null) {
throw new NullPointerException("Layout parameters cannot be null");
} else {
params = mParent.generateDefaultLayoutParams();
}
}
mLayoutParams = params;
requestLayout();
}
protected boolean checkLayoutParams(LayoutParams p) {
return p != null;
}
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new LayoutParams(p);
}
/**
* Gets the bounds of the given descendant that relative to this view.
*/
@@ -462,4 +765,206 @@ public class GLView {
getComponent(i).dumpTree(prefix + "....");
}
}
/**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height.
* A MeasureSpec is comprised of a size and a mode. There are three possible
* modes:
* <dl>
* <dt>UNSPECIFIED</dt>
* <dd>
* The parent has not imposed any constraint on the child. It can be whatever size
* it wants.
* </dd>
*
* <dt>EXACTLY</dt>
* <dd>
* The parent has determined an exact size for the child. The child is going to be
* given those bounds regardless of how big it wants to be.
* </dd>
*
* <dt>AT_MOST</dt>
* <dd>
* The child can be as large as it wants up to the specified size.
* </dd>
* </dl>
*
* MeasureSpecs are implemented as ints to reduce object allocation. This class
* is provided to pack and unpack the &lt;size, mode&gt; tuple into the int.
*/
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* The max size allowed
*/
public static final int MAX_SIZE = ~MODE_MASK;
/**
* Creates a measure specification based on the supplied size and mode.
*
* The mode must always be one of the following:
* <ul>
* <li>{@link #UNSPECIFIED}</li>
* <li>{@link #EXACTLY}</li>
* <li>{@link #AT_MOST}</li>
* </ul>
*
* <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
* implementation was such that the order of arguments did not matter
* and overflow in either value could impact the resulting MeasureSpec.
* {@link android.widget.RelativeLayout} was affected by this bug.
* Apps targeting API levels greater than 17 will get the fixed, more strict
* behavior.</p>
*
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
public static int makeMeasureSpec(int size, int mode) {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link #UNSPECIFIED},
* {@link #AT_MOST} or
* {@link #EXACTLY}
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
/**
* Returns a String representation of the specified measure
* specification.
*
* @param measureSpec the measure specification to convert to a String
* @return a String with the following format: "MeasureSpec: MODE SIZE"
*/
public static String toString(int measureSpec) {
int mode = getMode(measureSpec);
int size = getSize(measureSpec);
StringBuilder sb = new StringBuilder("MeasureSpec: ");
if (mode == UNSPECIFIED)
sb.append("UNSPECIFIED ");
else if (mode == EXACTLY)
sb.append("EXACTLY ");
else if (mode == AT_MOST)
sb.append("AT_MOST ");
else
sb.append(mode).append(" ");
sb.append(size);
return sb.toString();
}
}
/**
* The LayoutParams look like {@link android.view.ViewGroup.LayoutParams},
* and work like it.
*/
public static class LayoutParams {
/**
* Special value for the height or width requested by a View.
* MATCH_PARENT means that the view wants to be as big as its parent,
* minus the parent's padding, if any. Introduced in API Level 8.
*/
public static final int MATCH_PARENT = -1;
/**
* Special value for the height or width requested by a View.
* WRAP_CONTENT means that the view wants to be just large enough to fit
* its own internal content, taking its own padding into account.
*/
public static final int WRAP_CONTENT = -2;
/**
* Information about how wide the view wants to be. Can be one of the
* constants {@link #MATCH_PARENT} or {@link #WRAP_CONTENT} or an exact size.
*/
public int width;
/**
* Information about how tall the view wants to be. Can be one of the
* constants {@link #MATCH_PARENT} or {@link #WRAP_CONTENT} or an exact size.
*/
public int height;
/**
* Creates a new set of layout parameters with the specified width
* and height.
*
* @param width the width, either {@link #WRAP_CONTENT},
* {@link #MATCH_PARENT}, or a fixed size in pixels
* @param height the height, either {@link #WRAP_CONTENT},
* {@link #MATCH_PARENT}, or a fixed size in pixels
*/
public LayoutParams(int width, int height) {
this.width = width;
this.height = height;
}
/**
* Copy constructor. Clones the width and height values of the source.
*
* @param source The layout params to copy from.
*/
public LayoutParams(LayoutParams source) {
this.width = source.width;
this.height = source.height;
}
}
/**
* LayoutParams with gravity inside
*/
public static class GravityLayoutParams extends LayoutParams {
public int gravity = Gravity.NO_GRAVITY;
public GravityLayoutParams(GLView.LayoutParams source) {
super(source);
}
public GravityLayoutParams(int width, int height) {
super(width, height);
}
}
}