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.
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
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;
}
You can try using this library. Custom RecyclerView that implement features like ExpandableListView.
Related
Currently my divider is only drawing one width:
How would can I add an extra divider for every increment position in my recyclerview?
Here is my ItemDecoration class:
public SimpleDivider(Context mContext, ArrayList<Integer> mDepth) {
mDivider = ContextCompat.getDrawable(mContext, R.drawable.recycler_view_divider);
this.mContext = mContext;
this.mDepth = mDepth;
dividerMargin = 15;
}
#Override
public void onDraw(#NonNull Canvas c, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
int top = 0;
int bottom = parent.getHeight();
int childCount = parent.getChildCount();
for(int i = 0; i < childCount; ++i) {
int right = dividerMargin;
int left = 0;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
Edit1: Here's the Adapter. I thought it wouldn't be needed because all the logic would be written inside the ItemDecoration class.
private ArrayList<String> mList;
public class ViewHolder extends RecyclerView.ViewHolder{
TextView singleMessageComment;
public ViewHolder(#NonNull View itemView) {
super(itemView);
singleMessageComment = itemView.findViewById(R.id.item_child_comment);
}
}
public AdapterTest(ArrayList<String> mList) {
this.mList = mList;
}
#NonNull
#Override
public AdapterTest.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_single_layout, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull AdapterTest.ViewHolder viewHolder, int i) {
viewHolder.singleMessageComment.setText(mList.get(i));
}
#Override
public int getItemCount() {
return mList.size();
}
Adding decorations:
recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayout.VERTICAL));
recyclerView.addItemDecoration(new LeftDividerItemDecorator(this));
Declaration of the left divider item decorator:
public class LeftDividerItemDecorator extends RecyclerView.ItemDecoration {
private final Drawable mDivider;
private final Rect mBounds = new Rect();
private final Context mContext;
LeftDividerItemDecorator(Context context) {
mContext = context;
mDivider = context.getResources().getDrawable(R.drawable.divider);
}
public void onDraw(#NonNull Canvas c, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
if (parent.getLayoutManager() != null && mDivider != null) {
drawLeftDivider(c, parent);
}
}
private void drawLeftDivider(Canvas canvas, RecyclerView parent) {
canvas.save();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; ++i) {
View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, mBounds);
int childAdapterPosition = parent.getChildAdapterPosition(child);
int left = parent.getPaddingLeft();
// Solid size according to divider.xml width
//int right = left + (mDivider.getIntrinsicWidth());
// Dynamic size according to divider.xml width multiplied by child number
int right = left + (mDivider.getIntrinsicWidth() * (childAdapterPosition + 1));
int top = child.getTop();
int bottom = child.getBottom();
// Draw left vertical divider
mDivider.setBounds(
left,
top,
right,
bottom
);
mDivider.draw(canvas);
}
canvas.restore();
}
// Handles dividers width - move current views to right
public void getItemOffsets(#NonNull Rect outRect, #NonNull View view, #NonNull RecyclerView parent, #NonNull RecyclerView.State state) {
if (mDivider == null) {
outRect.set(0, 0, 0, 0);
} else {
int childAdapterPosition = parent.getChildAdapterPosition(view);
outRect.set(mDivider.getIntrinsicWidth() * childAdapterPosition, 0, 0, 0);
}
}
}
Divider's xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="4dp"
android:height="4dp" />
<solid android:color="#color/colorAccent" />
</shape>
Preview:
I want this type of tab indicator how i can achieve this, i have tried all solutions with drawable selectable handler but not getting anything
There is a simpler approach to achieve this by just providing a drawable of your custom indicator to the app:tabIndicator.
For example, in this case:
underline.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<item android:gravity="center">
<shape>
<size
android:width="22dp"
android:height="2dp" />
<corners android:radius="2dp" />
<solid android:color="#FF0000" />
</shape>
</item>
</layer-list>
and add it in your TabLayout in this way
<com.google.android.material.tabs.TabLayout
android:id="#+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/_16dp"
android:layout_marginTop="#dimen/_16dp"
app:tabIndicator="#drawable/underline"
app:tabIndicatorColor="#color/offers_header_text"
app:tabIndicatorHeight="4dp"
app:tabMode="scrollable"
app:tabRippleColor="#null" />
Try this:
public void wrapTabIndicatorToTitle(TabLayout tabLayout, int externalMargin, int internalMargin) {
View tabStrip = tabLayout.getChildAt(0);
if (tabStrip instanceof ViewGroup) {
ViewGroup tabStripGroup = (ViewGroup) tabStrip;
int childCount = ((ViewGroup) tabStrip).getChildCount();
for (int i = 0; i < childCount; i++) {
View tabView = tabStripGroup.getChildAt(i);
//set minimum width to 0 for instead for small texts, indicator is not wrapped as expected
tabView.setMinimumWidth(0);
// set padding to 0 for wrapping indicator as title
tabView.setPadding(0, tabView.getPaddingTop(), 0, tabView.getPaddingBottom());
// setting custom margin between tabs
if (tabView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) tabView.getLayoutParams();
if (i == 0) {
// left
settingMargin(layoutParams, externalMargin, internalMargin);
} else if (i == childCount - 1) {
// right
settingMargin(layoutParams, internalMargin, externalMargin);
} else {
// internal
settingMargin(layoutParams, internalMargin, internalMargin);
}
}
}
tabLayout.requestLayout();
}
}
private void settingMargin(ViewGroup.MarginLayoutParams layoutParams, int start, int end) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
layoutParams.setMarginStart(start);
layoutParams.setMarginEnd(end);
} else {
layoutParams.leftMargin = start;
layoutParams.rightMargin = end;
}
}
After setting the view pager in java file add :
wrapTabIndicatorToTitle(tabLayout,80,80);
One simple solution is:
tabLayout.setTabIndicatorFullWidth(false);
But it works with latest dependency like
implementation 'com.google.android.material:material:1.0.0'
For support library version 28.0.0, I have come up with a solution that replaces the TabLayout.tabViewContentBounds object with a custom one, so that you can apply your logic here to modify the indicator position anyway you want
here is the custom RectF class (thank goodness it's not a final class)
public class TabIndicatorRectF extends RectF implements Serializable {
private RectF temp = new RectF();
private IndicatorBoundsModifier indicatorBoundsModifier;
public TabIndicatorRectF(IndicatorBoundsModifier indicatorBoundsModifier) {
this.indicatorBoundsModifier = indicatorBoundsModifier;
}
private interface IndicatorBoundsModifier {
void modify(RectF bounds);
}
#Override
public void set(float left, float top, float right, float bottom) {
temp.set(left, top, right, bottom);
indicatorBoundsModifier.modify(temp);
super.set(temp);
}
public void replaceBoundsRectF(TabLayout tabLayout) {
try {
Field field = TabLayout.class.getDeclaredField("tabViewContentBounds");
field.setAccessible(true);
field.set(tabLayout, this);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static class FixedWidthModifier implements IndicatorBoundsModifier {
private float halfWidth;
public FixedWidthModifier(float width) {
this.halfWidth = Math.abs(width) / 2;
}
#Override
public void modify(RectF bounds) {
float centerX = bounds.centerX();
bounds.left = centerX - halfWidth;
bounds.right = centerX + halfWidth;
}
}
}
now call this
new TabIndicatorRectF(
new TabIndicatorRectF.FixedWidthModifier(
getResources().getDimension(R.dimen.tab_indicator_width)))
.replaceBoundsRectF(tabLayout);
I'm using the RecyclerView.ItemDecoration class to create dividers in the list, but I want to hide the divider for the last item in the list. Is this possible without having to implement the dividers myself?
You can try this,
public class SimpleDividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
public SimpleDividerItemDecoration(Context context) {
mDivider = ContextCompat.getDrawable(context, R.drawable.line_divider);
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getAdapter().getItemCount();
for (int i = 0; i < childCount; i++) {
if (i == (childCount - 1)) {
continue;
}
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
line_divider.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="1dp"
android:height="1dp" />
<solid android:color="#F5F5F5" />
</shape>
Try Kotlin version, inspired by #Muthukrishnan Rajendran
CommentDetailItemDecoration.kt
class CommentDetailItemDecoration(
context: Context
) : RecyclerView.ItemDecoration() {
val drawable: Drawable = ContextCompat.getDrawable(context, R.drawable.ft_item_divider)!!
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
val childCount = parent.adapter!!.itemCount
for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i)
if (child != null) {
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + drawable.intrinsicHeight
drawable.setBounds(left, top, right, bottom)
drawable.draw(c)
}
}
}
}
ft_item_divider.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="1dp"
android:height="1dp" />
<solid android:color="#color/grey_97" />
</shape>
[UPDATE]
You can simply copy or extend DividerItemDecoration class and change its drawing behaviour by modifying
for (int i = 0; i < childCount; i++) to for (int i = 0; i < childCount - 1; i++)
I have used the code from this answer to create a solid separator line for my RecyclerViews.
However, I would like the line to be dashed/dotted.
I already have a line_dashed.xml resource that I am using elsewhere in my app:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line" >
<stroke
android:color="#color/blue"
android:dashGap="12dp"
android:dashWidth="12dp"
android:width="1dp" />
</shape>
But if I try applying this as the drawable resource that is accessed via my call to recyclerView.addItemDecoration(new SimpleDividerItemDecoration(getContext())), no line is drawn at all.
How to solve so a dashed line is shown?
Just add your drawable resource into this item decorator.
DividerItemDecoration decorator = new DividerItemDecoration(ContextCompat.getDrawable(getContext(), R.drawable.line_dashed));
recyclerView.addItemDecoration(decorator);
and DividerItemDecorator class:
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int mPaddingLeft;
public DividerItemDecoration(Drawable divider) {
mDivider = divider;
mPaddingLeft = 0;
}
public DividerItemDecoration(Drawable divider, int paddingLeft) {
mDivider = divider;
mPaddingLeft = paddingLeft;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (mDivider == null) return;
if (parent.getChildAdapterPosition(view) < 1) return;
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
outRect.top = mDivider.getIntrinsicHeight();
} else {
outRect.left = mDivider.getIntrinsicWidth();
}
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mDivider == null) {
super.onDrawOver(c, parent, state);
return;
}
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
final int left = parent.getPaddingLeft() + mPaddingLeft;
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 1; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int size = mDivider.getIntrinsicHeight();
final int top = child.getTop() - params.topMargin;
final int bottom = top + size;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
} else { //horizontal
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 1; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int size = mDivider.getIntrinsicWidth();
final int left = child.getLeft() - params.leftMargin;
final int right = left + size;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
private int getOrientation(RecyclerView parent) {
if (parent.getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
return layoutManager.getOrientation();
} else
throw new IllegalStateException("DividerItemDecoration can only be used with a LinearLayoutManager.");
}
}
Should work I tested it.
UPDATE:
android:layerType="software"
add this parameter in xml for recyclerView
Also add size into your shape drawable:
<size android:height="1dp"/>
currently u can use DividerItemDecoration from the box.
recyclerView.apply {
layoutManager = LinearLayoutManager(this#YourFragment.context)
adapter = this#YourFragment.adapter
addItemDecoration(
DividerItemDecoration(
this#YourFragment.context,
DividerItemDecoration.VERTICAL
).apply {
context.getDrawable(R.drawable.divider)?.let {
setDrawable(it)
}
}
)
}
Use the next shape XML:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line">
<size android:height="1dp" />
<solid android:color="#color/primary" />
<stroke
android:width="0.5dp"
android:color="#color/primary"
android:dashWidth="5dp"
android:dashGap="5dp" />
</shape>
Attention: The stroke width must be less than the height of the line. In another case, the line will be not drawn.
Draw dashed line in Android is not so easy deal. Drowable like you showed and even just draw dotted line on canwas (canvas.drawLine(..., paintWithDashEffect)) not always works (not for all devices). You may use android:layerType="software" or draw path. IMHO, the better solution is to not draw dotted line at all (draw just line). But if you really need dotted line, you can use #fearless answer or somethink like this:
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
private int mDividerSize;
public DividerItemDecoration(int dividerSize) {
mDividerSize = dividerSize;
mPaint = new Paint();
mPaint.setColor(ContextCompat.getColor(context, R.color.colorAccent));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dividerSize);
mPaint.setPathEffect(new DashPathEffect(new float[]{dashGap,dashWidth},0));
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.bottom = mDividerSize;
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
Path path = new Path();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin + mDividerSize/2;
path.moveTo(left, top);
path.lineTo(right, top);
}
c.drawPath(path, mPaint);
}
}
Below is how I'm doing the spacing for RecyclerView items. It's designed to work with both grids and lists. The spacing works.
What I can't figure out is how to insert a divider line as well. Any help doing so would be greatly appreciated.
SIDE NOTE: if you have a better way to implement the spacing than what I'm currently doing, I'd be very grateful as well :)
public class ItemOffsetDecoration extends RecyclerView.ItemDecoration {
private int numOfColumns;
private int listSize;
private int offsetInDp;
private boolean isGridView;
private boolean canScrollHorizontally;
private boolean isBottomRow = false;
public ItemOffsetDecoration(RecyclerView.LayoutManager manager, int listSize, int offsetInDp) {
this(manager, 1, listSize, offsetInDp);
}
public ItemOffsetDecoration(RecyclerView.LayoutManager manager, int numOfColumns, int listSize, int offsetInDp) {
this.numOfColumns = numOfColumns;
this.listSize = listSize;
this.offsetInDp = PixelConversionUtils.dpToPx(offsetInDp);
this.isGridView = manager instanceof GridLayoutManager;
this.canScrollHorizontally = manager.canScrollHorizontally();
}
#Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) {
// only do left/right spacing if grid or horizontal list
if (isGridView || canScrollHorizontally) {
outRect.left = offsetInDp;
outRect.right = offsetInDp;
}
// only do top/bottom spacing if grid or vertical list
if (isGridView || !canScrollHorizontally) {
int pos = parent.getChildAdapterPosition(view);
boolean isNotTopRow = pos >= numOfColumns;
// Don't add top spacing to top row
if (isNotTopRow) {
outRect.top = offsetInDp;
}
int columnIndex = ((GridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
if (pos >= (listSize - numOfColumns) && columnIndex == 0) {
isBottomRow = true;
}
// Don't add bottom spacing to bottom row
if (!isBottomRow && pos < (listSize - numOfColumns)) {
outRect.bottom = offsetInDp;
}
}
}
}
here's a quick visual of what I'm looking to do:
here's what I have:
here's what I want:
You can achieve desired look this way:
first, create a divider Drawable, for this example I've used a simple shape, but you could use default line divider or any other drawable:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="2dp" />
<size android:width="2dp" />
<solid android:color="#000000" />
</shape>
second, in your ItemOffsetDecoration declare Drawable and initialize it:
public class ItemOffsetDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
...
public ItemOffsetDecoration(...) {
mDivider = ContextCompat.getDrawable(context, R.drawable.item_divider);
}
}
third, override onDrawOver() method:
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (isGridView) {
drawVerticalDivider(c, parent);
} else {
drawVerticalDivider(c, parent);
drawHorizontalDivider(c, parent);
}
}
where drawVerticalDivider() & drawHorizontalDivider() are (might be a good idea to refactor them into the single method and control direction of the divider via parameter):
public void drawVerticalDivider(Canvas c, RecyclerView parent) {
if (parent.getChildCount() == 0) return;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params =
(RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getLeft() - params.leftMargin - offsetInDp;
int right = child.getRight() + params.rightMargin + offsetInDp;
int top = child.getBottom() + params.bottomMargin + offsetInDp;
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontalDivider(Canvas c, RecyclerView parent) {
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params =
(RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getRight() + params.rightMargin + offsetInDp;
int right = left + mDivider.getIntrinsicWidth();
int top = child.getTop() - params.topMargin - offsetInDp;
int bottom = child.getBottom() + params.bottomMargin + offsetInDp;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
Result for the Linear and Grid LayoutManagers:
Try placing the following XML snippet to get a divider:
<View android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="72dp"
android:layout_marginRight="16dp"
android:background="123e4152"/>
You can put this in the recyclerView's item layout beneath your items. Also Play around with the margins and background to suit your list.
haha……actualy,i had try like this for Divider ,although with a bit funny : first make you recycleview backgroud with Deep color,and make item_view backgroud white,then marginbottom for every item -> I'm serious, do not vote down :)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_marginBottom="2dp"
android:layout_height="wrap_content">
<TextView
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<TextView
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>