今天遇到一个奇怪的问题,不知道大家是否遇到过。关于RecyclerView的item中的TextView设置跑马灯的问题(Android TV开发)。需要实现的效果是:当我选中RecyclerView的一个item,如果该item中的tvTitle内容长度超出它的宽度,就实现跑马灯效果。问题:当我在RecyclerView中按下键使焦点选中内容长度超过tvTitle宽度(即tvTitle需要滚动的item时),焦点会跑飞(焦点回到页面最初的焦点位置)。
一、问题做法
1、首先我是在Adapter的onBindViewHolder中给ViewHolder设置Tag。(之前一直这样做,后来也发现不是ViewHolder设置Tag的问题)
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, intposition) {
ViewHolder viewHolder = (ViewHolder) holder;
viewHolder.itemView.setTag(viewHolder); // 做标记ViewHolder
...
}
2、然后同样在Adapter中写跑马灯的方法。
private View mLastMarqueeView;
// 外面直接调用改方法,传入item即可
public void updateViewMarquee(View view){
if(mLastMarqueeView!= null) {
setViewMarquee(mLastMarqueeView, false);
}
setViewMarquee(view, true,position);
mLastMarqueeView= view;
}
private void setViewMarquee(View view, booleanmarquee){
if(view ==null)
return;
ViewHolder holder = (ViewHolder) view.getTag(); // 获取标记ViewHolder
if(holder !=null && holder.tvTitle!= null){
if(marquee) {
holder.tvTitle.setMarqueeRepeatLimit(-1);
holder.tvTitle.setEllipsize(TextUtils.TruncateAt.MARQUEE);
}else{
holder.tvTitle.setMarqueeRepeatLimit(0);
holder.tvTitle.setEllipsize(TextUtils.TruncateAt.END);
}
}
}
3、在外面该item获得焦点的地方调用updateViewMarquee(View view)方法,使选中该item后,该item中的tvTitle实现跑马灯效果。
/**
* 每项item的获取焦点监听(此处为自定义RecyclerViewTV中封装好的接口)
*/
private RecyclerViewTV.OnItemListenermSpecificOnItemListener =new RecyclerViewTV.OnItemListener() {
@Override
public voidonItemSelected(RecyclerViewTV parent,View itemView,
int position){
mOldView= itemView;
// 当item获取到焦点,让该item的tvTitle实现跑马灯
mRecSpecAdapter.updateViewMarquee(itemView);
}
};
(当然也可在xml中直接设置android:marqueeRepeatLimit="marquee_forever" android:singleLine="true" android:ellipsize="marquee"实现跑马灯)(问题一样还存在,如果将android:ellipsize="end"不会有焦点跑丢的现象,但是就无法实现跑马灯效果)
二、发现问题
刚开始一直以为是RecyclerView的item不能setTag()为viewHolder,后来发现问题是RecyclerView中显示了布局的重用,所以当从不需要跑马灯的tvTitle所在的item到需要跑马灯的tvTitle的item时,因为布局的重用。使用RecyclerView丢失的焦点,当然丢失焦点后,该页面的焦点则被页面最起初的控件(可获得焦点的控件)获取到焦点,因此出现我所遇到的情况。
三、换方法实现效果
经过反复的调试,不让ViewHolder重用等等的方法都试过了,还是没能解决。最后只能换了另外一种方法实现了项目中需要的效果。大致思路为:在Adapter的onBindViewHolder()方法中就判断该item中的tvTitle的内容长度是否超出宽度,如果超出,则将ellipsize设置为marquee,不超出的设置为end。这样就有效的避免了RecyclerView布局重用造成的不必要的问题。其中需要方法isOverFlowed(TextView tv)来判断内容是否超出宽度。
实现方法如下:
1、 TextView中的xml设置
android:singleLine="true"
android:marqueeRepeatLimit="marquee_forever"
android:ellipsize="end"
2、
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, intposition) {
ViewHolder viewHolder = (ViewHolder) holder;
tvTitle.setText("《"+m.getTitle()+"》");
if(isOverFlowed(tvTitle)){
tvTitle.setMarqueeRepeatLimit(-1);
tvTitle.setEllipsize(TextUtils.TruncateAt.MARQUEE);
}else{
tvTitle.setMarqueeRepeatLimit(0);
tvTitle.setEllipsize(TextUtils.TruncateAt.END);
}
}
/**
* 判断TextView的内容宽度是否超出其可用宽度
* @paramtv
*@return
*/
public static boolean isOverFlowed(TextView tv) {
intavailableWidth = tv.getWidth() - tv.getPaddingLeft() - tv.getPaddingRight();
Paint textViewPaint = tv.getPaint();
float textWidth = textViewPaint.measureText(tv.getText().toString());
if (textWidth > availableWidth) {
return true;
}else {
return false;
}
}
虽然这个不是最佳的实现方法,但是也能达到了预期的效果,规避了原先的问题。至于原先问题,还有待探究。如上有什么问题,欢迎大神们指正;如果有更好的实现方法还望指教!