【移动应用开发技术】怎么在Android中利用ItemDecoration 实现一个分组索引列表_第1页
【移动应用开发技术】怎么在Android中利用ItemDecoration 实现一个分组索引列表_第2页
【移动应用开发技术】怎么在Android中利用ItemDecoration 实现一个分组索引列表_第3页
【移动应用开发技术】怎么在Android中利用ItemDecoration 实现一个分组索引列表_第4页
【移动应用开发技术】怎么在Android中利用ItemDecoration 实现一个分组索引列表_第5页
已阅读5页,还剩13页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

【移动应用开发技术】怎么在Android中利用ItemDecoration实现一个分组索引列表

怎么在Android中利用ItemDecoration实现一个分组索引列表?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。分组GroupHeader分割线SideBar前两个部分涉及到一个ItemDecoration类,也是我们接下来的重点,该类是RecyclerView的一个抽象静态内部类,主要作用就是给RecyclerView的ItemView绘制额外的装饰效果,例如给RecyclerView添加分割线。使用ItemDecoration时需要继承该类,根据需求可以重写如下三个方法,其它的方法已经deprecated了:public

class

GroupHeaderItemDecoration

extends

RecyclerView.ItemDecoration

{

@Override

public

void

getItemOffsets(Rect

outRect,

View

view,

RecyclerView

parent,

RecyclerView.State

state)

{

super.getItemOffsets(outRect,

view,

parent,

state);

}

@Override

public

void

onDraw(Canvas

c,

RecyclerView

parent,

RecyclerView.State

state)

{

super.onDraw(c,

parent,

state);

}

@Override

public

void

onDrawOver(Canvas

c,

RecyclerView

parent,

RecyclerView.State

state)

{

super.onDrawOver(c,

parent,

state);

}

}然后将其添加到RecyclerView中:recyclerView.addItemDecoration(new

GroupHeaderItemDecoration())了解这个三个方法的作用,这样才能更好的实现我们想要的功能:1、getItemOffsets()给指定的ItemView设置偏移量,具体怎么设置呢,咱们看图说话:图中左边的是原始RecyclerView列表,右边是设置了ItemView偏移量的列表,其实相当于在ItemView外部添加了一个矩形区域其中left、top、right、bottom就是ItemView在四个方向的偏移量,对应的设置代码如下:outRect.set(left,

top,

right,

bottom)在我们的分组索引列表中,只需要对ItemView设置顶部的偏移量,其它三个偏移量为0即可。这样就可以在ItemView顶部预留出一定高度的区域,如下图:2、onDraw()在getItemOffsets()方法中,我们设置了偏移量,进而得到了对应的偏移区域,接下来在onDraw()中就可以给ItemView绘制装饰效果了,所以我们在该方法中将分组索引列表中的GroupHeader的内容绘制在ItemView顶部偏移区域里。也就是绘制前边gif图里的A、B、C...GroupHeader,虽然看起来像一个个独立的ItemView,但并不是的哦!注意该绘制操作会在ItemView的onDraw()前完成的!3、onDrawOver()该方法同样也是用来绘制的,但是它在ItemDecoration的onDraw()方法和ItemView的onDraw()完成后才执行。所以其绘制的内容会遮挡在RecyclerView上,因此我们可以在该方法中绘制分组索引列表中悬浮的GroupHeader,也就是在列表顶部随着列表滚动切换的GroupHeader。一、分组GroupHeader三个方法的作用已经解释完了,接下来就是代码实现我们的效果了:首先保证RecyclerView的数据源已经按照某种规律进行了分组排序,具体什么规律你说了算,我们例子中按照数据源中指定字段的值的首字母升序排列,也就是常见通讯录的排序方式。然后在每个data中保存需要在GroupHeader上显示的内容,可以使用tag字段,我们这里保存的是对应的首字母。这里没必要将整个数据源设置到ItemDecoration里边,所以我们只需要提取排序后数据源的tag保存到列表中,然后设置到ItemDecoration里边,后边的操作就依赖设置的数据源了,根据tag的异同来决定是否绘制GroupHeader等。上边已经分析了,GroupHeader只在列表中每组数据对应的第一个ItemView顶部显示,只需要对ItemView设置顶部的偏移量即可:public

class

GroupHeaderItemDecoration

extends

RecyclerView.ItemDecoration

{

@Override

public

void

getItemOffsets(Rect

outRect,

View

view,

RecyclerView

parent,

RecyclerView.State

state)

{

super.getItemOffsets(outRect,

view,

parent,

state);

RecyclerView.LayoutManager

manager

=

parent.getLayoutManager();

//只处理线性垂直类型的列表

if

((manager

instanceof

LinearLayoutManager)

&&

LinearLayoutManager.VERTICAL

!=

((LinearLayoutManager)

manager).getOrientation())

{

return;

}

int

position

=

parent.getChildAdapterPosition(view);

//ItemView的position==0

或者

当前ItemView的data的tag和上一个ItemView的不相等,则为当前ItemView设置top

偏移量

if

(!Utils.listIsEmpty(tags)

&&

(position

==

0

||

!tags.get(position).equals(tags.get(position

-

1))))

{

outRect.set(0,

groupHeaderHeight,

0,

0);

}

}

@Override

public

void

onDraw(Canvas

c,

RecyclerView

parent,

RecyclerView.State

state)

{

super.onDraw(c,

parent,

state);

}

@Override

public

void

onDrawOver(Canvas

c,

RecyclerView

parent,

RecyclerView.State

state)

{

super.onDrawOver(c,

parent,

state);

}

}其中tags就是我们设置到ItemDecoration的数据源,是一个String集合。groupHeaderHeight就是ItemView的顶部偏移量。之后就是在ItemView的顶部偏移区域绘制GroupHeader了:public

class

GroupHeaderItemDecoration

extends

RecyclerView.ItemDecoration

{

@Override

public

void

getItemOffsets(Rect

outRect,

View

view,

RecyclerView

parent,

RecyclerView.State

state)

{

super.getItemOffsets(outRect,

view,

parent,

state);

}

@Override

public

void

onDraw(Canvas

c,

RecyclerView

parent,

RecyclerView.State

state)

{

super.onDraw(c,

parent,

state);

for

(int

i

=

0;

i

<

parent.getChildCount();

i++)

{

View

view

=

parent.getChildAt(i);

int

position

=

parent.getChildAdapterPosition(view);

String

tag

=

tags.get(position);

//和getItemOffsets()里的条件判断类似,开始绘制分组的GroupHeader

if

(!Utils.listIsEmpty(tags)

&&

(position

==

0

||

!tag.equals(tags.get(position

-

1))))

{

drawGroupHeader(c,

parent,

view,

tag);

}

}

}

@Override

public

void

onDrawOver(Canvas

c,

RecyclerView

parent,

RecyclerView.State

state)

{

super.onDrawOver(c,

parent,

state);

}

private

void

drawGroupHeader(Canvas

c,

RecyclerView

parent,

View

view,

String

tag)

{

RecyclerView.LayoutParams

params

=

(RecyclerView.LayoutParams)

view.getLayoutParams();

int

left

=

parent.getPaddingLeft();

int

right

=

parent.getWidth()

-

parent.getPaddingRight();

int

bottom

=

view.getTop()

-

params.topMargin;

int

top

=

bottom

-

groupHeaderHeight;

c.drawRect(left,

top,

right,

bottom,

mPaint);

int

x

=

left

+

groupHeaderLeftPadding;

int

y

=

top

+

(groupHeaderHeight

+

Utils.getTextHeight(mTextPaint,

tag))

/

2;

c.drawText(tag,

x,

y,

mTextPaint);

}

}绘制GroupHeader就是Canvasc操作,先绘制一个矩形框,再绘制相应的文字,当然绘制图片也是没问题的,其中groupHeaderLeftPadding是个可配置字段,代表绘制的文字或图片到列表左边沿的距离,也可以理解为GroupHeader的左padding。最后就是悬浮在顶部的GroupHeader绘制了:public

class

GroupHeaderItemDecoration

extends

RecyclerView.ItemDecoration

{

@Override

public

void

getItemOffsets(Rect

outRect,

View

view,

RecyclerView

parent,

RecyclerView.State

state)

{

super.getItemOffsets(outRect,

view,

parent,

state);

}

@Override

public

void

onDraw(Canvas

c,

RecyclerView

parent,

RecyclerView.State

state)

{

super.onDraw(c,

parent,

state);

}

@Override

public

void

onDrawOver(Canvas

c,

RecyclerView

parent,

RecyclerView.State

state)

{

super.onDrawOver(c,

parent,

state);

if

(!show)

{

return;

}

//列表第一个可见的ItemView位置

int

position

=

((LinearLayoutManager)

(parent.getLayoutManager())).findFirstVisibleItemPosition();

String

tag

=

tags.get(position);

View

view

=

parent.findViewHolderForAdapterPosition(position).itemView;

//当前ItemView的data的tag和下一个itemView的不相等,则代表将要重新绘制悬停的GroupHeader

boolean

flag

=

false;

if

(!Utils.listIsEmpty(tags)

&&

(position

+

1)

<

tags.size()

&&

!tag.equals(tags.get(position

+

1)))

{

//如果第一个可见ItemView的底部坐标小于groupHeaderHeight,则执行Canvas向上位移操作

if

(view.getBottom()

<=

groupHeaderHeight)

{

c.save();

flag

=

true;

c.translate(0,

view.getHeight()

+

view.getTop()

-

groupHeaderHeight);

}

}

drawSuspensionGroupHeader(c,

parent,

tag);

if

(flag)

{

c.restore();

}

}

private

void

drawSuspensionGroupHeader(Canvas

c,

RecyclerView

parent,

String

tag)

{

int

left

=

parent.getPaddingLeft();

int

right

=

parent.getWidth()

-

parent.getPaddingRight();

int

bottom

=

groupHeaderHeight;

int

top

=

0;

c.drawRect(left,

top,

right,

bottom,

mPaint);

int

x

=

left

+

groupHeaderLeftPadding;

int

y

=

top

+

(groupHeaderHeight

+

Utils.getTextHeight(mTextPaint,

tag))

/

2;

c.drawText(tag,

x,

y,

mTextPaint);

}

}绘制操作和onDraw中的类似,gif中有一个悬浮GroupHeader上移的动画,就是通过Canvas位移来实现的,注意在Canvas位移的前后进行save()和restore()操作。我们给GroupHeaderItemDecoration提供了设置GroupHeader左padding、高度、背景色、文字颜色、尺寸、以及是否显示顶部悬浮GroupHeader的方法,方便使用。关于绘制操作需要注意的是,GroupHeader所在的偏移区域和ItemView是相互独立的,不要把GroupHeader当做ItemView的一部分哦。到这里GroupHeader的功能就实现了,只需要将GroupHeaderItemDecoration添加到RecyclerView即可。至于如何通过layout或者View来实现GroupHeader,做过一些尝试,效果都不理想,期待大家的好想法哦!这里先用一个接口,对外提供自定义绘制GroupHeader的方法:public

interface

OnDrawItemDecorationListener

{

/**

*

绘制GroupHeader

*

@param

c

*

@param

paint

绘制GroupHeader区域的paint

*

@param

textPaint

绘制文字的paint

*

@param

params

共四个值left、top、right、bottom

代表GroupHeader所在区域的四个坐标值

*

@param

position

原始数据源中的position

*/

void

onDrawGroupHeader(Canvas

c,

Paint

paint,

TextPaint

textPaint,

int[]

params,

int

position);

/**

*

绘制悬浮在列表顶部的GroupHeader

*/

void

onDrawSuspensionGroupHeader(Canvas

c,

Paint

paint,

TextPaint

textPaint,

int[]

params,

int

position);

}二、分割线现在RecyclerView还差一个分割线,当前最笨的办法可以在ItemView的布局文件中设置,既然系统都提供了ItemDecoration,那用它来优雅的实现为何不可呢,我们只需要给列表中每组数据除了最后一项数据对应的ItemView之外的添加分割线即可,也就是不给每组数据对应的最后一个ItemView添加分割线。很简单,直接上核心代码:public

class

DivideItemDecoration

extends

RecyclerView.ItemDecoration

{

@Override

public

void

getItemOffsets(Rect

outRect,

View

view,

RecyclerView

parent,

RecyclerView.State

state)

{

super.getItemOffsets(outRect,

view,

parent,

state);

RecyclerView.LayoutManager

manager

=

parent.getLayoutManager();

//只处理线性垂直类型的列表

if

((manager

instanceof

LinearLayoutManager)

&&

LinearLayoutManager.VERTICAL

!=

((LinearLayoutManager)

manager).getOrientation())

{

return;

}

int

position

=

parent.getChildAdapterPosition(view);

if

(!Utils.listIsEmpty(tags)

&&

(position

+

1)

<

tags.size()

&&

tags.get(position).equals(tags.get(position

+

1)))

{

//当前ItemView的data的tag和下一个ItemView的不相等,则为当前ItemView设置bottom

偏移量

outRect.set(0,

0,

0,

divideHeight);

}

}

@Override

public

void

onDraw(Canvas

c,

RecyclerView

parent,

RecyclerView.State

state)

{

super.onDraw(c,

parent,

state);

for

(int

i

=

0;

i

<

parent.getChildCount();

i++)

{

View

view

=

parent.getChildAt(i);

int

position

=

parent.getChildAdapterPosition(view);

//和getItemOffsets()里的条件判断类似

if

(!Utils.listIsEmpty(tags)

&&

(position

+

1)

<

tags.size()

&&

tags.get(position).equals(tags.get(position

+

1)))

{

drawDivide(c,

parent,

view);

}

}

}

@Override

public

void

onDrawOver(Canvas

c,

RecyclerView

parent,

RecyclerView.State

state)

{

super.onDrawOver(c,

parent,

state);

}

private

void

drawDivide(Canvas

c,

RecyclerView

parent,

View

view)

{

RecyclerView.LayoutParams

params

=

(RecyclerView.LayoutParams)

view.getLayoutParams();

int

left

=

parent.getPaddingLeft();

int

right

=

parent.getWidth();

int

top

=

view.getBottom()

+

params.bottomMargin;

int

bottom

=

top

+

divideHeight;

c.drawRect(left,

top,

right,

bottom,

mPaint);

}

}三、SideBarSideBar就是gif图右边的垂直字符条,是一个自定义View。手指触摸选中一个字符,则列表会滚动到对应的分组头部位置。实现起来也蛮简单的,核心代码如下:public

class

SideBar

extends

View

{

@Override

protected

void

onMeasure(int

widthMeasureSpec,

int

heightMeasureSpec)

{

super.onMeasure(widthMeasureSpec,

heightMeasureSpec);

int

widthSize

=

MeasureSpec.getSize(widthMeasureSpec);

int

widthMode

=

MeasureSpec.getMode(widthMeasureSpec);

int

heightSize

=

MeasureSpec.getSize(heightMeasureSpec);

int

heightMode

=

MeasureSpec.getMode(heightMeasureSpec);

//重新计算SideBar宽高

if

(heightMode

==

MeasureSpec.AT_MOST

||

widthMode

==

MeasureSpec.AT_MOST)

{

getMaxTextSize();

if

(heightMode

==

MeasureSpec.AT_MOST)

{

heightSize

=

(maxHeight

+

15)

*

indexArray.length;

}

if

(widthMode

==

MeasureSpec.AT_MOST)

{

widthSize

=

maxWidth

+

10;

}

}

setMeasuredDimension(widthSize,

heightSize);

}

@Override

protected

void

onDraw(Canvas

canvas)

{

for

(int

i

=

0;

i

<

indexArray.length;

i++)

{

String

index

=

indexArray[i];

float

x

=

(mWidth

-

mTextPaint.measureText(index))

/

2;

float

y

=

mMarginTop

+

mHeight

*

i

+

(mHeight

+

Utils.getTextHeight(mTextPaint,

index))

/

2;

//绘制字符

canvas.drawText(index,

x,

y,

mTextPaint);

}

}

@Override

public

boolean

onTouchEvent(MotionEvent

event)

{

switch

(event.getAction())

{

case

MotionEvent.ACTION_DOWN:

case

MotionEvent.ACTION_MOVE:

//

选中字符的下标

int

pos

=

(int)

((event.getY()

-

mMarginTop)

/

mHeight);

if

(pos

>=

0

&&

pos

<

indexArray.length)

{

setBackgroundColor(TOUCH_COLOR);

if

(onSideBarTouchListener

!=

null)

{

for

(int

i

=

0;

i

<

tags.size();

i++)

{

if

(indexArray[pos].equals(tags.get(i)))

{

onSideBarTouchListener.onTouch(indexArray[pos],

i);

break;

}

else

{

onSideBarTouchListener.onTouch(indexArray[pos],

-1);

}

}

}

}

break;

case

MotionEvent.ACTION_UP:

case

MotionEvent.ACTION_CANCEL:

setBackgroundColor(UNTOUCH_COLOR);

if

(onSideBarTouchListener

!=

null)

{

onSideBarTouchListener.onTouchEnd();

}

break;

}

return

true;

}

}在onMeasure()方法里,如果SideBar的宽、高测量模式为MeasureSpec.AT_MOST则重新计算SideBar的宽、高。onDraw()方法则是遍历索引数组,并绘制字符索引。在onTouchEvent()方法里,我们根据手指在SideBar上触摸坐标点的y值,计算出触摸的相应字符,以便在OnSideBarTouchListener接口进行后续操作,例如列表的跟随滚动等等。四、实例前边已经完成了三大核心功能,最后来愉快的使用下吧:public

class

MainActivity

extends

AppCompatActivity

{

@Override

protected

void

onCreate(Bundle

savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

RecyclerView

recyclerView

=

(RecyclerView)

findViewById(R.id.list);

SideBar

sideBar

=

(SideBar)

findViewById(R.id.side_bar);

final

TextView

tip

=

(TextView)

findViewById(R.id.tip);

final

List<ItemData>

datas

=

new

ArrayList<>();

ItemData

data

=

new

ItemData("北京");

datas.add(data);

ItemData

data1

=

new

ItemData("上海");

datas.add(data1);

ItemData

data2

=

new

ItemData("广州");

datas.add(data2);

.

.

.

ItemData

data34

=

new

ItemData("Hello

China");

datas.add(data34);

ItemData

data35

=

new

ItemData("宁波");

datas.add(data35);

SortHelper<ItemData>

sortHelper

=

new

SortHelper<ItemData>()

{

@Override

public

String

sortField(ItemData

data)

{

return

data.getTitle();

}

};

sortHelper.sortByLetter(datas);//将数据源按指定字段首字母排序

List<String>

tags

=

sortHelper.getTags(datas);//提取已排序数据源的tag值

MyAdapter

adapter

=

new

MyAdapter(this,

datas,

false);

final

LinearLayoutManager

layoutManager

=

new

LinearLayoutManager(this);

layoutManager.setOrientation(LinearLayoutManager.VERTICAL);

recyclerView.setLayoutManager(layoutManager);

//添加分割线

recyclerView.addItemDecoration(new

DivideItemDecoration().setTags(tags));

//添加GroupHeader

recyclerView.addItemDecoration(new

GroupHeaderItemDecoration(this)

.setTags(tags)//设置tag集合

.setGroupHeaderHeight(30)//设置GroupHeader高度

.setGroupHeaderLeftPadding(20));//设置GroupHeader

左padding

recyclerView.setAdapter(adapter);

sideBar.setOnSideBarTouchListener(tags,

new

OnSideBarTouchListener()

{

@Override

public

void

onTouch(String

text,

int

position)

{

tip.setVisibility(View.VISIBLE);

tip.setText(text);

if

("↑".equals(text))

{

layoutManager.scrollToPositionWithOffset(0,

0);

return;

}

//滚动列表到指定位置

if

(position

!=

-1)

{

layoutManager.scrollToPositionWithOffset(position,

0);

}

}

@Override

public

void

onTouchEnd()

{

tip.setVisibility(View.GONE);

}

});

}

}这也就是文章开头的gif效果。如果需要自定义ItemView的绘制可以这样写:recyclerView.addItemDecoration(new

GroupHeaderItemDecoration(this)

.setTags(tags)

.setGroupHeaderHeight(30)

.setGroupHeaderLeftPadding(20)

.setOnDrawItemDecorationListener(new

OnDra

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论