[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:
parent
9cce95b839
commit
180360409c
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue