am 5c597c1d: am a8854564: am ce4a9d91: Merge "New custom widgets library" into lmp-mr1-dev

* commit '5c597c1d89c529675096c2d326c452145403979d':
  New custom widgets library
This commit is contained in:
Diego Perez
2015-06-12 15:10:18 +00:00
committed by Android Git Automerger
7 changed files with 357 additions and 2 deletions

View File

@@ -0,0 +1,8 @@
<component name="ArtifactManager">
<artifact type="jar" name="studio-android-widgets:jar">
<output-path>$PROJECT_DIR$/out/artifacts/studio_android_widgets_jar</output-path>
<root id="archive" name="studio-android-widgets.jar">
<element id="module-output" name="studio-android-widgets" />
</root>
</artifact>
</component>

View File

@@ -0,0 +1,8 @@
<component name="ArtifactManager">
<artifact type="jar" name="studio-android-widgets-src:jar">
<output-path>$PROJECT_DIR$/out/artifacts/studio_android_widgets_src_jar</output-path>
<root id="archive" name="studio-android-widgets-src.jar">
<element id="dir-copy" path="$PROJECT_DIR$/studio-custom-widgets/src" />
</root>
</artifact>
</component>

View File

@@ -4,7 +4,7 @@
<modules>
<module fileurl="file://$PROJECT_DIR$/bridge/bridge.iml" filepath="$PROJECT_DIR$/bridge/bridge.iml" />
<module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" />
<module fileurl="file://$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" filepath="$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" />
</modules>
</component>
</project>
</project>

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.android.tools.idea.editors.theme.widgets;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* {@link ViewGroup} that wraps another view and catches any possible exceptions that the child view
* might generate.
* This is used by the theme editor to stop custom views from breaking the preview.
*/
// TODO: This view is just a temporary solution that will be replaced by adding a try / catch
// for custom views in the ClassConverter
public class ErrorCatcher extends ViewGroup {
public ErrorCatcher(Context context) {
super(context);
}
public ErrorCatcher(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ErrorCatcher(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ErrorCatcher(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
assert getChildCount() == 1 : "ErrorCatcher can only have one child";
View child = getChildAt(0);
try {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(resolveSize(child.getMeasuredWidth(), widthMeasureSpec),
resolveSize(child.getMeasuredHeight(), heightMeasureSpec));
} catch (Throwable t) {
Bridge.getLog().warning(LayoutLog.TAG_BROKEN, "Failed to do onMeasure for view " +
child.getClass().getCanonicalName(), t);
setMeasuredDimension(resolveSize(0, widthMeasureSpec),
resolveSize(0, heightMeasureSpec));
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
try {
return super.drawChild(canvas, child, drawingTime);
} catch (Throwable t) {
Bridge.getLog().warning(LayoutLog.TAG_BROKEN, "Failed to draw for view " +
child.getClass().getCanonicalName(), t);
}
return false;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
assert getChildCount() == 1 : "ErrorCatcher can only have one child";
View child = getChildAt(0);
try {
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
} catch (Throwable e) {
Bridge.getLog().warning(LayoutLog.TAG_BROKEN, "Failed to do onLayout for view " +
child.getClass().getCanonicalName(), e);
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.android.tools.idea.editors.theme.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Button;
@SuppressWarnings("unused")
public class PressedButton extends Button {
public PressedButton(Context context, AttributeSet attrs) {
super(context, attrs);
setPressed(true);
jumpDrawablesToCurrentState();
}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.android.tools.idea.editors.theme.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
/**
* Custom layout used in the theme editor to display the component preview. It arranges the child
* Views as a grid of cards.
* <p/>
* The Views are measured and the maximum width and height are used to dimension all the child
* components. Any margin attributes from the children are ignored and only the item_margin element
* is used.
*/
@SuppressWarnings("unused")
public class ThemePreviewLayout extends ViewGroup {
private final int mMaxColumns;
private final int mMaxColumnWidth;
private final int mMinColumnWidth;
private final int mItemHorizontalMargin;
private final int mItemVerticalMargin;
/** Item width to use for every card component. This includes margins. */
private int mItemWidth;
/** Item height to use for every card component. This includes margins. */
private int mItemHeight;
/** Calculated number of columns */
private int mNumColumns;
public ThemePreviewLayout(Context context) {
this(context, null);
}
public ThemePreviewLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ThemePreviewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (attrs == null) {
mMaxColumnWidth = Integer.MAX_VALUE;
mMinColumnWidth = 0;
mMaxColumns = Integer.MAX_VALUE;
mItemHorizontalMargin = 0;
mItemVerticalMargin = 0;
return;
}
DisplayMetrics dm = getResources().getDisplayMetrics();
int maxColumnWidth = attrs.getAttributeIntValue(null, "max_column_width", Integer
.MAX_VALUE);
int minColumnWidth = attrs.getAttributeIntValue(null, "min_column_width", 0);
int itemHorizontalMargin = attrs.getAttributeIntValue(null, "item_horizontal_margin", 0);
int itemVerticalMargin = attrs.getAttributeIntValue(null, "item_vertical_margin", 0);
mMaxColumnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
maxColumnWidth,
dm);
mMinColumnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
minColumnWidth,
dm);
mItemHorizontalMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
itemHorizontalMargin,
dm);
mItemVerticalMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
itemVerticalMargin,
dm);
mMaxColumns = attrs.getAttributeIntValue(null, "max_columns", Integer.MAX_VALUE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Measure the column size.
// The column has a minimum width that will be used to calculate the maximum number of
// columns that we can fit in the available space.
//
// Once we have the maximum number of columns, we will span all columns width evenly to fill
// all the available space.
int wSize = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
// Calculate the desired width of all columns and take the maximum.
// This step can be skipped if we have a fixed column height so we do not have to
// dynamically calculate it.
int childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int itemWidth = 0;
int itemHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
if (v.getVisibility() == GONE) {
continue;
}
measureChild(v, childWidthSpec, childHeightSpec);
itemWidth = Math.max(itemWidth, v.getMeasuredWidth());
itemHeight = Math.max(itemHeight, v.getMeasuredHeight());
}
itemWidth = Math.min(Math.max(itemWidth, mMinColumnWidth), mMaxColumnWidth);
mNumColumns = Math.min((int) Math.ceil((double) wSize / itemWidth), mMaxColumns);
// Check how much space this distribution would take taking into account the margins.
// If it's bigger than what we have, remove one column.
int wSizeNeeded = mNumColumns * itemWidth + (mNumColumns - 1) * mItemHorizontalMargin;
if (wSizeNeeded > wSize && mNumColumns > 1) {
mNumColumns--;
}
if (getChildCount() < mNumColumns) {
mNumColumns = getChildCount();
}
if (mNumColumns == 0) {
mNumColumns = 1;
}
// Inform each child of the measurement
childWidthSpec = MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY);
childHeightSpec = MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY);
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
if (v.getVisibility() == GONE) {
continue;
}
measureChild(v, childWidthSpec, childHeightSpec);
}
// Calculate the height of the first column to measure our own size
int firstColumnItems = getChildCount() / mNumColumns + ((getChildCount() % mNumColumns) > 0
? 1 : 0);
int horizontalMarginsTotalWidth = (mNumColumns - 1) * mItemHorizontalMargin;
int verticalMarginsTotalHeight = (firstColumnItems - 1) * mItemVerticalMargin;
int totalWidth = mNumColumns * itemWidth + horizontalMarginsTotalWidth +
mPaddingRight + mPaddingLeft;
int totalHeight = firstColumnItems * itemHeight + verticalMarginsTotalHeight +
mPaddingBottom + mPaddingTop;
setMeasuredDimension(resolveSize(totalWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec));
mItemWidth = itemWidth;
mItemHeight = itemHeight;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int itemsPerColumn = getChildCount() / mNumColumns;
// The remainder items are distributed one per column.
int remainderItems = getChildCount() % mNumColumns;
int x = mPaddingLeft;
int y = mPaddingTop;
int position = 1;
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
v.layout(x,
y,
x + mItemWidth,
y + mItemHeight);
if (position == itemsPerColumn + (remainderItems > 0 ? 1 : 0)) {
// Break column
position = 1;
remainderItems--;
x += mItemWidth + mItemHorizontalMargin;
y = mPaddingTop;
} else {
position++;
y += mItemHeight + mItemVerticalMargin;
}
}
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
<orderEntry type="library" name="framework.jar" level="project" />
<orderEntry type="module" module-name="bridge" />
</component>
</module>