问题
I have a view that uses the ExpandableListView that has a ton of logic around it and in the adapters. For e.g., it looks like this
I have a requirement to display the same view with a different skin that has the expand/collapse hidden and has a border around parent and its children, something like this
I see attributes to have border for the whole control or just parent or individual child but nothing to have a border around parent and its children.
Has anyone done something like this? Short of not using Expandablelistview and recreating the view, is there anyway I can achieve the border?
Edit 1:
Here is a gist that has the template for what I am trying to do.
Edit 2:
I have a solution playing with parent and child borders,
setting parent to ┎─┒
and all-but-last children to ┃ ┃
and last child to ┖─┚
Here is the gist for the solution I have so far
I am still open to a better solution and will offer the bounty to anything that is less kludge than my solution.
回答1:
EDIT So I've added ItemDecoration
feature to ExpandableListView
, It's pretty much works like the RecyclerView
's ItemDecoration
, here is the code:
Subclass the ExpandableListView
public class ExpandableListViewItemDecoration extends ExpandableListView {
private List<ItemDecorationListView> itemDecorations = new ArrayList<>(1);
/* ... */
public void addItemDecoration(ItemDecorationListView item){
itemDecorations.add(item);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
final int count = itemDecorations.size();
for (int i = 0; i < count; i++) {
itemDecorations.get(i).onDrawOver(canvas, this);
}
}
ItemDecorationListView:
public abstract class ItemDecorationListView {
public void onDrawOver(Canvas c, ListView parent) {
}
}
The ItemDecorator:
public class ItemDecoratorBorderListView extends ItemDecorationListView {
private final Paint paint = new Paint();
private final int size;
public ItemDecoratorBorderListView(int size, @ColorInt int color) {
this.size = size;
paint.setColor(color);
paint.setStrokeWidth(size);
paint.setStyle(Paint.Style.STROKE);
}
public static final String TAG = ItemDecoratorBorderListView.class.getSimpleName();
@Override
public void onDrawOver(Canvas c, ListView parent) {
super.onDrawOver(c, parent);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
if (isHeader(child, parent, i)) {
for (int j = i + 1; j < childCount; j++) {
View childEnd = parent.getChildAt(j);
boolean end = isHeader(childEnd, parent, i) || j == childCount - 1;
if (end) {
if (BuildConfig.DEBUG) { Log.d(TAG, String.format(Locale.ENGLISH, "Draw called i: %d, j: %d", i, j)); }
childEnd = parent.getChildAt(j - 1);
if (j == childCount - 1) { childEnd = parent.getChildAt(j); }
float top = child.getTop() + child.getTranslationY() + size + child.getPaddingTop();
float bottom = childEnd.getBottom() + childEnd.getTranslationY() - size - childEnd
.getPaddingBottom();
float right = child.getRight() + child.getTranslationX() - size - child.getPaddingRight();
float left = child.getLeft() + child.getTranslationX() + size + child.getPaddingLeft();
c.drawRect(left, top, right, bottom, paint);
i = j - 1;
break;
}
}
}
}
}
public boolean isHeader(View child, ListView parent, int position) {
//You need to set an Id for your layout
return child.getId() == R.id.header;
}
}
And just add it to your ExpandableListView
:
expandableList.addItemDecoration(new ItemDecoratorBorderListView(
getResources().getDimensionPixelSize(R.dimen.stroke_size),
Color.GRAY
));
Old Answer:
This is an implementation with RecyclerView
and ItemDecoration
, I've already written this solution before knowing you're stuck with legacy code, So I'm sharing this anyway.
Item Decoration:
public class ItemDecoratorBorder extends RecyclerView.ItemDecoration {
private final Paint paint = new Paint();
private final int size;
public ItemDecoratorBorder(int size, @ColorInt int color) {
this.size = size;
paint.setColor(color);
paint.setStrokeWidth(size);
paint.setStyle(Paint.Style.STROKE);
}
public static final String TAG = ItemDecoratorBorder.class.getSimpleName();
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (parent.getLayoutManager() == null) { return; }
int childCount = parent.getChildCount();
RecyclerView.LayoutManager lm = parent.getLayoutManager();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
if (isHeader(child, parent)) {
for (int j = i + 1; j < childCount; j++) {
View childEnd = parent.getChildAt(j);
boolean end = isHeader(childEnd, parent) || j == childCount - 1;
if (end) {
if (BuildConfig.DEBUG) { Log.d(TAG, String.format(Locale.ENGLISH, "Draw called i: %d, j: %d", i, j)); }
childEnd = parent.getChildAt(j - 1);
if (j == childCount - 1) {
childEnd = parent.getChildAt(j);
}
float top = child.getTop() + child.getTranslationY() + size + child.getPaddingTop();
float bottom = lm.getDecoratedBottom(childEnd) + childEnd.getTranslationY() - size - childEnd.getPaddingBottom();
float right = lm.getDecoratedRight(child) + child.getTranslationX() - size - child.getPaddingRight();
float left = lm.getDecoratedLeft(child) + child.getTranslationX() + size + child.getPaddingLeft();
c.drawRect(left, top, right, bottom, paint);
i = j - 1;
break;
}
}
}
}
}
public boolean isHeader(View child, RecyclerView parent) {
int viewType = parent.getLayoutManager().getItemViewType(child);
return viewType == R.layout.layout_header;
}
I'm finding where a group starts and ends using the view types and draw a rectangle around the start and end position.
The code is available at my github repo
回答2:
Well I have a solution for you, but It's better to use recycleView instead of listView, However, We can draw line for every sides e.g:
for parent group it will be something like ┎─┒
and for all child's without the last child it will be something like: ┎ ┒
and for the last child it will be like : ──
.
The code: `groupbg.xml:
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#000" />
</shape>
</item>
<item android:left="2dp" android:top="2dp" android:right="2dp">
<shape android:shape="rectangle">
<solid android:color="#fff" />
</shape>
</item>
</layer-list>
normalchild.xml:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#000" />
</shape>
</item>
<item android:left="2dp" android:right="2dp">
<shape android:shape="rectangle">
<solid android:color="#fff" />
</shape>
</item>
bottomchild.xml:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#000" />
</shape>
</item>
<item android:bottom="2dp">
<shape android:shape="rectangle">
<solid android:color="#fff" />
</shape>
</item>
No set it to your adapter:
private int childrenCount;
@Override
public int getChildrenCount(int groupPosition) {
return childrenCount = data.get(groupPosition).getItems().length;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
View view;
if (convertView == null){
view = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
}
else {
view = convertView;
}
view.setBackground(context.getResources().getDrawable(R.drawable.groupbg));
TextView lblNumber = view.findViewById(R.id.lblNumber);
TextView lblName = view.findViewById(R.id.lblName);
lblNumber.setText((groupPosition + 1) + ".");
lblName.setText(((TestModel)getGroup(groupPosition)).getCategory());
return view;
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
View view;
if (convertView == null){
view = LayoutInflater.from(context).inflate(R.layout.item_child, parent, false);
}
else {
view = convertView;
}
TextView lblNumber = view.findViewById(R.id.lblNumber);
TextView lblName = view.findViewById(R.id.lblName);
lblNumber.setText((childPosition + 1)+ ".");
lblName.setText((String)getChild(groupPosition, childPosition));
if (childPosition < childrenCount)
view.setBackground(context.getResources().getDrawable(R.drawable.normalchild));
else view.setBackground(context.getResources().getDrawable(R.drawable.bottomchild));
return view;
}
回答3:
You can try using this library. Custom RecyclerView that implement features like ExpandableListView.
来源:https://stackoverflow.com/questions/49408666/expandablelistview-border-around-parent-and-its-children