diff options
author | Diego Perez <diegoperez@google.com> | 2015-06-12 14:34:48 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-06-12 14:34:50 +0000 |
commit | ce4a9d9110436a2ea825976af7895c58303a3dac (patch) | |
tree | 6cd68743652c5a84cf0a1c6b056741174e4110b7 | |
parent | f5a99437bf203eca49e8ab8f74f2c206c551289b (diff) | |
parent | aa3b2043f0403d064afcf0977f6e713662aceaff (diff) | |
download | frameworks_base-ce4a9d9110436a2ea825976af7895c58303a3dac.zip frameworks_base-ce4a9d9110436a2ea825976af7895c58303a3dac.tar.gz frameworks_base-ce4a9d9110436a2ea825976af7895c58303a3dac.tar.bz2 |
Merge "New custom widgets library" into lmp-mr1-dev
7 files changed, 357 insertions, 2 deletions
diff --git a/tools/layoutlib/.idea/artifacts/studio_android_widgets_jar.xml b/tools/layoutlib/.idea/artifacts/studio_android_widgets_jar.xml new file mode 100644 index 0000000..0450be3 --- /dev/null +++ b/tools/layoutlib/.idea/artifacts/studio_android_widgets_jar.xml @@ -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>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/artifacts/studio_android_widgets_src_jar.xml b/tools/layoutlib/.idea/artifacts/studio_android_widgets_src_jar.xml new file mode 100644 index 0000000..a844ca3 --- /dev/null +++ b/tools/layoutlib/.idea/artifacts/studio_android_widgets_src_jar.xml @@ -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>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/modules.xml b/tools/layoutlib/.idea/modules.xml index 684f4fd..9bdc381 100644 --- a/tools/layoutlib/.idea/modules.xml +++ b/tools/layoutlib/.idea/modules.xml @@ -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>
\ No newline at end of file diff --git a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ErrorCatcher.java b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ErrorCatcher.java new file mode 100644 index 0000000..ecf39b3 --- /dev/null +++ b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ErrorCatcher.java @@ -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); + } + } +} diff --git a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/PressedButton.java b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/PressedButton.java new file mode 100644 index 0000000..4320157 --- /dev/null +++ b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/PressedButton.java @@ -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(); + } +} diff --git a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ThemePreviewLayout.java b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ThemePreviewLayout.java new file mode 100644 index 0000000..af89910 --- /dev/null +++ b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ThemePreviewLayout.java @@ -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; + } + } + } +} + + diff --git a/tools/layoutlib/studio-custom-widgets/studio-android-widgets.iml b/tools/layoutlib/studio-custom-widgets/studio-android-widgets.iml new file mode 100644 index 0000000..b0363d7 --- /dev/null +++ b/tools/layoutlib/studio-custom-widgets/studio-android-widgets.iml @@ -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>
\ No newline at end of file |