[SuwLib] Add dividers to GLIF list layouts

Add dividers with inset support to GLIF list and recycler layouts.
Typical usage of this will be by specifying app:suwDividerInset to
either @dimen/suw_items_text_divider_inset or
@dimen/suw_items_icon_divider_inset (the default).

Bug: 25726515
Change-Id: I8f569680d71d1baba093b20f3d48570d53383acb
This commit is contained in:
Maurice Lam 2015-11-13 16:21:39 -08:00
parent 9cce95b839
commit 180360409c
13 changed files with 658 additions and 21 deletions

View file

@ -0,0 +1,166 @@
/*
* 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.setupwizardlib;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* An {@link android.support.v7.widget.RecyclerView.ItemDecoration} for RecyclerView to draw
* dividers between items. This ItemDecoration will draw the drawable specified by
* {@link #setDivider(android.graphics.drawable.Drawable)} as the divider in between each item by
* default, and the behavior of whether the divider is shown can be customized by subclassing
* {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}.
*
* <p>Modified from v14 PreferenceFragment.DividerDecoration, added with inset capabilities.
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
/* static section */
public interface DividedViewHolder {
/**
* Returns whether divider is allowed above this item. A divider will be shown only if both
* items immediately above and below it allows this divider.
*/
boolean isDividerAllowedAbove();
/**
* Returns whether divider is allowed below this item. A divider will be shown only if both
* items immediately above and below it allows this divider.
*/
boolean isDividerAllowedBelow();
}
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider,
android.R.attr.dividerHeight
};
/**
* Creates a default instance of {@link DividerItemDecoration}, using
* {@code android:attr/listDivider} as the divider and {@code android:attr/dividerHeight} as the
* divider height.
*/
public static DividerItemDecoration getDefault(Context context) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
final Drawable divider = a.getDrawable(0);
final int dividerHeight = a.getDimensionPixelSize(1, 0);
a.recycle();
final DividerItemDecoration decoration = new DividerItemDecoration();
decoration.setDivider(divider);
decoration.setDividerHeight(dividerHeight);
return decoration;
}
/* non-static section */
private Drawable mDivider;
private int mDividerHeight;
private int mDividerIntrinsicHeight;
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mDivider == null) {
return;
}
final int childCount = parent.getChildCount();
final int width = parent.getWidth();
final int dividerHeight = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight;
for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
final View view = parent.getChildAt(childViewIndex);
if (shouldDrawDividerBelow(view, parent)) {
final int top = (int) ViewCompat.getY(view) + view.getHeight();
mDivider.setBounds(0, top, width, top + dividerHeight);
mDivider.draw(c);
}
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
if (shouldDrawDividerBelow(view, parent)) {
outRect.bottom = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight;
}
}
private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
if ((holder instanceof DividedViewHolder)
&& !((DividedViewHolder) holder).isDividerAllowedBelow()) {
// Don't draw if the current view holder doesn't allow drawing below
return false;
}
final int index = parent.indexOfChild(view);
final int lastItemIndex = parent.getChildCount() - 1;
if (index == lastItemIndex) {
return false;
}
if (index < lastItemIndex) {
final View nextView = parent.getChildAt(index + 1);
final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
if ((nextHolder instanceof DividedViewHolder) &&
!((DividedViewHolder) nextHolder).isDividerAllowedAbove()) {
// Don't draw if the next view holder doesn't allow drawing above
return false;
}
}
return true;
}
/**
* Sets the drawable to be used as the divider.
*/
public void setDivider(Drawable divider) {
if (divider != null) {
mDividerIntrinsicHeight = divider.getIntrinsicHeight();
} else {
mDividerIntrinsicHeight = 0;
}
mDivider = divider;
}
/**
* Gets the drawable currently used as the divider.
*/
public Drawable getDivider() {
return mDivider;
}
/**
* Sets the divider height, in pixels.
*/
public void setDividerHeight(int dividerHeight) {
mDividerHeight = dividerHeight;
}
/**
* Gets the divider height, in pixels.
*/
public int getDividerHeight() {
return mDividerHeight;
}
}

View file

@ -19,6 +19,8 @@ package com.android.setupwizardlib;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@ -32,6 +34,7 @@ import android.widget.TextView;
import com.android.setupwizardlib.items.ItemGroup;
import com.android.setupwizardlib.items.ItemInflater;
import com.android.setupwizardlib.items.RecyclerItemAdapter;
import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper;
import com.android.setupwizardlib.view.HeaderRecyclerView;
/**
@ -43,6 +46,10 @@ public class GlifRecyclerLayout extends GlifLayout {
private RecyclerView mRecyclerView;
private TextView mHeaderTextView;
private ImageView mIconView;
private DividerItemDecoration mDividerDecoration;
private Drawable mDefaultDivider;
private Drawable mDivider;
private int mDividerInset;
public GlifRecyclerLayout(Context context) {
this(context, 0, 0);
@ -76,9 +83,25 @@ public class GlifRecyclerLayout extends GlifLayout {
final ItemGroup inflated = (ItemGroup) new ItemInflater(context).inflate(xml);
setAdapter(new RecyclerItemAdapter(inflated));
}
int dividerInset =
a.getDimensionPixelSize(R.styleable.SuwGlifRecyclerLayout_suwDividerInset, 0);
if (dividerInset == 0) {
dividerInset = getResources()
.getDimensionPixelSize(R.dimen.suw_items_icon_divider_inset);
}
setDividerInset(dividerInset);
a.recycle();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mDivider == null) {
// Update divider in case layout direction has just been resolved
updateDivider();
}
}
@Override
protected View onInflateTemplate(LayoutInflater inflater, int template) {
if (template == 0) {
@ -97,13 +120,16 @@ public class GlifRecyclerLayout extends GlifLayout {
@Override
protected void onTemplateInflated() {
final Context context = getContext();
mRecyclerView = (RecyclerView) findViewById(R.id.suw_recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mRecyclerView.setLayoutManager(new LinearLayoutManager(context));
if (mRecyclerView instanceof HeaderRecyclerView) {
final View header = ((HeaderRecyclerView) mRecyclerView).getHeader();
mHeaderTextView = (TextView) header.findViewById(R.id.suw_layout_title);
mIconView = (ImageView) header.findViewById(R.id.suw_layout_icon);
}
mDividerDecoration = DividerItemDecoration.getDefault(context);
mRecyclerView.addItemDecoration(mDividerDecoration);
}
@Override
@ -127,4 +153,40 @@ public class GlifRecyclerLayout extends GlifLayout {
public RecyclerView.Adapter getAdapter() {
return getRecyclerView().getAdapter();
}
/**
* Sets the start inset of the divider. This will use the default divider drawable set in the
* theme and inset it {@code inset} pixels to the right (or left in RTL layouts).
*
* @param inset The number of pixels to inset on the "start" side of the list divider. Typically
* this will be either {@code @dimen/suw_items_icon_divider_inset} or
* {@code @dimen/suw_items_text_divider_inset}.
*/
public void setDividerInset(int inset) {
mDividerInset = inset;
updateDivider();
}
public int getDividerInset() {
return mDividerInset;
}
private void updateDivider() {
boolean shouldUpdate = true;
if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
shouldUpdate = isLayoutDirectionResolved();
}
if (shouldUpdate) {
if (mDefaultDivider == null) {
mDefaultDivider = mDividerDecoration.getDivider();
}
mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable(mDefaultDivider,
mDividerInset /* start */, 0 /* top */, 0 /* end */, 0 /* bottom */, this);
mDividerDecoration.setDivider(mDivider);
}
}
public Drawable getDivider() {
return mDivider;
}
}

View file

@ -0,0 +1,49 @@
/*
* 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.setupwizardlib.items;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.android.setupwizardlib.DividerItemDecoration;
class ItemViewHolder extends RecyclerView.ViewHolder
implements DividerItemDecoration.DividedViewHolder {
private boolean mIsEnabled;
public ItemViewHolder(View itemView) {
super(itemView);
}
@Override
public boolean isDividerAllowedAbove() {
return mIsEnabled;
}
@Override
public boolean isDividerAllowedBelow() {
return mIsEnabled;
}
public void setEnabled(boolean isEnabled) {
mIsEnabled = isEnabled;
itemView.setClickable(isEnabled);
itemView.setEnabled(isEnabled);
itemView.setFocusable(isEnabled);
}
}

View file

@ -29,7 +29,7 @@ import com.android.setupwizardlib.R;
* create this adapter can be inflated by {@link com.android.setupwizardlib.items.ItemInflater} from
* XML.
*/
public class RecyclerItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder>
implements ItemHierarchy.Observer {
private static final int[] SELECTABLE_ITEM_BACKGROUND = new int[] {
@ -40,13 +40,6 @@ public class RecyclerItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
void onItemSelected(IItem item);
}
private static class GenericViewHolder extends RecyclerView.ViewHolder {
public GenericViewHolder(View itemView) {
super(itemView);
}
}
private final ItemHierarchy mItemHierarchy;
private OnItemSelectedListener mListener;
@ -76,10 +69,10 @@ public class RecyclerItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final View view = inflater.inflate(viewType, parent, false);
final GenericViewHolder viewHolder = new GenericViewHolder(view);
final ItemViewHolder viewHolder = new ItemViewHolder(view);
final TypedArray typedArray = parent.getContext()
.obtainStyledAttributes(SELECTABLE_ITEM_BACKGROUND);
@ -105,18 +98,10 @@ public class RecyclerItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
public void onBindViewHolder(ItemViewHolder holder, int position) {
final IItem item = getItem(position);
item.onBindView(holder.itemView);
if (item.isEnabled()) {
holder.itemView.setClickable(true);
holder.itemView.setEnabled(true);
holder.itemView.setFocusable(true);
} else {
holder.itemView.setClickable(false);
holder.itemView.setEnabled(false);
holder.itemView.setFocusable(false);
}
holder.setEnabled(item.isEnabled());
}
@Override

View file

@ -0,0 +1,113 @@
/*
* 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.setupwizardlib.test;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
import android.view.ViewGroup;
import com.android.setupwizardlib.DividerItemDecoration;
public class DividerItemDecorationTest extends AndroidTestCase {
@SmallTest
public void testDivider() {
final DividerItemDecoration decoration = new DividerItemDecoration();
Drawable divider = new ColorDrawable();
decoration.setDivider(divider);
assertSame("Divider should be same as set", divider, decoration.getDivider());
}
@SmallTest
public void testDividerHeight() {
final DividerItemDecoration decoration = new DividerItemDecoration();
decoration.setDividerHeight(123);
assertEquals("Divider height should be 123", 123, decoration.getDividerHeight());
}
@SmallTest
public void testShouldDrawDividerBelow() {
// Set up the canvas to be drawn
Bitmap bitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(bitmap);
// Set up the item decoration, with 1px red divider line
final DividerItemDecoration decoration = new DividerItemDecoration();
Drawable divider = new ColorDrawable(Color.RED);
decoration.setDivider(divider);
decoration.setDividerHeight(1);
// Set up recycler view with vertical linear layout manager
RecyclerView testRecyclerView = new RecyclerView(getContext());
testRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
// Set up adapter with 3 items, each 5px tall
testRecyclerView.setAdapter(new RecyclerView.Adapter() {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
final View itemView = new View(getContext());
itemView.setMinimumWidth(20);
itemView.setMinimumHeight(5);
return new RecyclerView.ViewHolder(itemView) {
};
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
}
@Override
public int getItemCount() {
return 3;
}
});
testRecyclerView.layout(0, 0, 20, 20);
decoration.onDraw(canvas, testRecyclerView, null);
// Draw the expected result on a bitmap
Bitmap expectedBitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_4444);
Canvas expectedCanvas = new Canvas(expectedBitmap);
Paint paint = new Paint();
paint.setColor(Color.RED);
expectedCanvas.drawRect(0, 5, 20, 6, paint);
expectedCanvas.drawRect(0, 10, 20, 11, paint);
// Compare the two bitmaps
assertBitmapEquals(expectedBitmap, bitmap);
}
private void assertBitmapEquals(Bitmap expected, Bitmap actual) {
assertEquals("Width should be the same", expected.getWidth(), actual.getWidth());
assertEquals("Height should be the same", expected.getHeight(), actual.getHeight());
for (int x = 0; x < expected.getWidth(); x++) {
for (int y = 0; y < expected.getHeight(); y++) {
assertEquals("Pixel at (" + x + ", " + y + ") should be the same",
expected.getPixel(x, y), actual.getPixel(x, y));
}
}
}
}

View file

@ -17,6 +17,9 @@
package com.android.setupwizardlib.test;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
@ -94,6 +97,21 @@ public class GlifRecyclerLayoutTest extends InstrumentationTestCase {
}
}
@SmallTest
public void testDividerInset() {
GlifRecyclerLayout layout = new TestLayout(mContext);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
}
assertRecyclerTemplateInflated(layout);
layout.setDividerInset(10);
assertEquals("Divider inset should be 10", 10, layout.getDividerInset());
final Drawable divider = layout.getDivider();
assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable);
}
private void assertRecyclerTemplateInflated(GlifRecyclerLayout layout) {
View recyclerView = layout.findViewById(R.id.suw_recycler_view);
assertTrue("@id/suw_recycler_view should be a RecyclerView",

View file

@ -30,6 +30,7 @@
<!-- Custom view attributes -->
<attr name="suwHeader" format="reference" />
<attr name="suwHeaderText" format="string" localization="suggested" />
<attr name="suwDividerInset" format="dimension|reference" />
<declare-styleable name="SuwIllustration">
<attr name="suwAspectRatio" format="float" />
@ -51,10 +52,12 @@
<declare-styleable name="SuwGlifListLayout">
<attr name="android:entries" />
<attr name="suwDividerInset" />
</declare-styleable>
<declare-styleable name="SuwGlifRecyclerLayout">
<attr name="android:entries" />
<attr name="suwDividerInset" />
</declare-styleable>
<declare-styleable name="SuwSetupWizardLayout">

View file

@ -82,6 +82,9 @@
<dimen name="suw_items_padding_vertical">15dp</dimen>
<dimen name="suw_items_verbose_padding_vertical">11dp</dimen>
<dimen name="suw_items_icon_divider_inset">72dp</dimen>
<dimen name="suw_items_text_divider_inset">24dp</dimen>
<!-- Extra padding in the bottom to compensate for difference between descent and (top) internal leading -->
<dimen name="suw_items_padding_bottom_extra">1dp</dimen>
<dimen name="suw_items_verbose_padding_bottom_extra">5dp</dimen>

View file

@ -19,6 +19,8 @@ package com.android.setupwizardlib;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@ -30,6 +32,7 @@ import android.widget.ListView;
import com.android.setupwizardlib.items.ItemAdapter;
import com.android.setupwizardlib.items.ItemGroup;
import com.android.setupwizardlib.items.ItemInflater;
import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper;
/**
* A GLIF themed layout with a ListView. {@code android:entries} can also be used to specify an
@ -37,8 +40,16 @@ import com.android.setupwizardlib.items.ItemInflater;
*/
public class GlifListLayout extends GlifLayout {
/* static section */
private static final String TAG = "GlifListLayout";
/* non-static section */
private ListView mListView;
private Drawable mDivider;
private Drawable mDefaultDivider;
private int mDividerInset;
public GlifListLayout(Context context) {
this(context, 0, 0);
@ -72,9 +83,25 @@ public class GlifListLayout extends GlifLayout {
final ItemGroup inflated = (ItemGroup) new ItemInflater(context).inflate(xml);
setAdapter(new ItemAdapter(inflated));
}
int dividerInset =
a.getDimensionPixelSize(R.styleable.SuwGlifListLayout_suwDividerInset, 0);
if (dividerInset == 0) {
dividerInset = getResources()
.getDimensionPixelSize(R.dimen.suw_items_icon_divider_inset);
}
setDividerInset(dividerInset);
a.recycle();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mDivider == null) {
// Update divider in case layout direction has just been resolved
updateDivider();
}
}
@Override
protected View onInflateTemplate(LayoutInflater inflater, int template) {
if (template == 0) {
@ -107,4 +134,41 @@ public class GlifListLayout extends GlifLayout {
public ListAdapter getAdapter() {
return getListView().getAdapter();
}
/**
* Sets the start inset of the divider. This will use the default divider drawable set in the
* theme and inset it {@code inset} pixels to the right (or left in RTL layouts).
*
* @param inset The number of pixels to inset on the "start" side of the list divider. Typically
* this will be either {@code @dimen/suw_items_icon_divider_inset} or
* {@code @dimen/suw_items_text_divider_inset}.
*/
public void setDividerInset(int inset) {
mDividerInset = inset;
updateDivider();
}
public int getDividerInset() {
return mDividerInset;
}
private void updateDivider() {
boolean shouldUpdate = true;
if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
shouldUpdate = isLayoutDirectionResolved();
}
if (shouldUpdate) {
final ListView listView = getListView();
if (mDefaultDivider == null) {
mDefaultDivider = listView.getDivider();
}
mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable(mDefaultDivider,
mDividerInset /* start */, 0 /* top */, 0 /* end */, 0 /* bottom */, this);
listView.setDivider(mDivider);
}
}
public Drawable getDivider() {
return mDivider;
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.setupwizardlib.util;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.view.View;
/**
* Provides convenience methods to handle drawable layout directions in different SDK versions.
*/
public class DrawableLayoutDirectionHelper {
/**
* Creates an {@link android.graphics.drawable.InsetDrawable} according to the layout direction
* of {@code view}.
*/
public static InsetDrawable createRelativeInsetDrawable(Drawable drawable,
int insetStart, int insetTop, int insetEnd, int insetBottom, View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
&& view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
return new InsetDrawable(drawable, insetEnd, insetTop, insetStart, insetBottom);
} else {
return new InsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom);
}
}
/**
* Creates an {@link android.graphics.drawable.InsetDrawable} according to
* {@code layoutDirection}.
*/
public static InsetDrawable createRelativeInsetDrawable(Drawable drawable,
int insetStart, int insetTop, int insetEnd, int insetBottom, int layoutDirection) {
if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
return new InsetDrawable(drawable, insetEnd, insetTop, insetStart, insetBottom);
} else {
return new InsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom);
}
}
}

View file

@ -0,0 +1,101 @@
/*
* 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.setupwizardlib.test;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper;
public class DrawableLayoutDirectionHelperTest extends AndroidTestCase {
@SmallTest
public void testCreateRelativeInsetDrawableLtr() {
final Drawable drawable = new ColorDrawable(Color.RED);
final InsetDrawable insetDrawable =
DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable,
1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */,
View.LAYOUT_DIRECTION_LTR);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
assertSame("Drawable from getDrawable() should be same as passed in", drawable,
insetDrawable.getDrawable());
}
Rect outRect = new Rect();
insetDrawable.getPadding(outRect);
assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4),
outRect);
}
@SmallTest
public void testCreateRelativeInsetDrawableRtl() {
final Drawable drawable = new ColorDrawable(Color.RED);
final InsetDrawable insetDrawable =
DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable,
1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */,
View.LAYOUT_DIRECTION_RTL);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
assertSame("Drawable from getDrawable() should be same as passed in", drawable,
insetDrawable.getDrawable());
}
Rect outRect = new Rect();
insetDrawable.getPadding(outRect);
assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4),
outRect);
}
@SmallTest
public void testCreateRelativeInsetDrawableViewRtl() {
final Drawable drawable = new ColorDrawable(Color.RED);
final View view = new ForceRtlView(getContext());
final InsetDrawable insetDrawable =
DrawableLayoutDirectionHelper.createRelativeInsetDrawable(drawable,
1 /* start */, 2 /* top */, 3 /* end */, 4 /* bottom */, view);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
assertSame("Drawable from getDrawable() should be same as passed in", drawable,
insetDrawable.getDrawable());
}
Rect outRect = new Rect();
insetDrawable.getPadding(outRect);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
assertEquals("InsetDrawable padding should be same as inset", new Rect(3, 2, 1, 4),
outRect);
} else {
assertEquals("InsetDrawable padding should be same as inset", new Rect(1, 2, 3, 4),
outRect);
}
}
private static class ForceRtlView extends View {
public ForceRtlView(Context context) {
super(context);
}
@Override
public int getLayoutDirection() {
return View.LAYOUT_DIRECTION_RTL;
}
}
}

View file

@ -17,6 +17,9 @@
package com.android.setupwizardlib.test;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.ContextThemeWrapper;
@ -94,6 +97,21 @@ public class GlifListLayoutTest extends InstrumentationTestCase {
}
}
@SmallTest
public void testDividerInset() {
GlifListLayout layout = new GlifListLayout(mContext);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
layout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
}
assertListTemplateInflated(layout);
layout.setDividerInset(10);
assertEquals("Divider inset should be 10", 10, layout.getDividerInset());
final Drawable divider = layout.getDivider();
assertTrue("Divider should be instance of InsetDrawable", divider instanceof InsetDrawable);
}
private void assertListTemplateInflated(GlifListLayout layout) {
View title = layout.findViewById(R.id.suw_layout_title);
assertNotNull("@id/suw_layout_title should not be null", title);