1.实现动态显示,隐藏ListView的Item部分View.效果如下图所示:
点击右侧的按钮,显示或者隐藏底部的操作布局,实现Item的部分布局的动态显示.
1.inflate方法的不同参数的含义,以及作用.
2.View的绘制过程,主要包含onMeasure,onLayout,onDraw这三个方法.
3.自定义View.
4.如何控制Item的部分布局的显示,隐藏.
实现思路通过标记一个全局变量来记录当前是那个Item正在被操作(右侧按钮被点击),初始化的position是-1,表示所有的底部操作布局都是隐藏的.
/***
* 标记操作下表的位置:从0开始
*/
public int mShowBottomPosition = -1;
同时需要一个状态来标记当前被操作的Item的状态(隐藏或者显示).
/**
* 表示当前position的状态
*/
public int mCurrentPositionStatus = HIDE_BOTTOM;
public static final int SHOW_BOTTOM = 1;
public static final int HIDE_BOTTOM = 0;
为Item的右侧按钮添加点击事件,改变mShowBottomPosition的数值为当前点击的position.同时修改mCurrentPositionStatus的状态,如果被点击的Item的底部布局之前处于隐藏状态,则mCurrentPositionStatus = SHOW_BOTTOM,否则mCurrentPositionStatus = HIDE_BOTTOM.然后执行notifyDataSetChanged方法,重新绘制布局.
为右侧的按钮添加点击事件.代码如下:
holder.mGoImage.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
showOrHideBottom(holder, position);
}
});
/**
* 显示或者隐藏底部控制器
*
* @param position 位置
*/
private void showOrHideBottom(ViewHolder holder, int position)
{
LogUtils.i(TAG, "mShowBottomPosition111 :" + mShowBottomPosition + " position :" + position);
if (mShowBottomPosition != position)
{
mShowBottomPosition = position;
mCurrentPositionStatus = SHOW_BOTTOM;
} else
{
mShowBottomPosition = position;
if (mCurrentPositionStatus == HIDE_BOTTOM)
{
mCurrentPositionStatus = SHOW_BOTTOM;
} else
{
mCurrentPositionStatus = HIDE_BOTTOM;
}
}
mAdapter.notifyDataSetChanged();
}
然后在getView方法里面调用setBottomStatusByPosition()方法,这个方法才是真正的控制Item的底部布局的显示与隐藏的代码.代码如下:
/**
* 设置底部操作的状态
*
* @param position
*/
private void setBottomStatusByPosition(final View convertView, ViewHolder holder, final int position)
{
((MeasureViewHeightLayout)convertView).setOnMeasureAfterInterface(null);
LogUtils.i(TAG, "mShowBottomPosition2222 :" + mShowBottomPosition + " position :" + position);
if (mShowBottomPosition == position)
{
int status = mCurrentPositionStatus;
LogUtils.i(TAG, "mShowBottomPosition22 status :" + status);
if (status == SHOW_BOTTOM)
{
holder.bottom_action_bar.setVisibility(VISIBLE);
holder.statusArrorImg.setImageResource(R.mipmap.close_bottom_operation_icon);
if(mFileList.get(position).isDir())
{
holder.op_shared.setVisibility(GONE);
}
else
{
holder.op_shared.setVisibility(VISIBLE);
}
if(convertView != null)
{
MeasureViewHeightLayout.onMeasureAfterInterface onMeasureAfterInterface
= new MeasureViewHeightLayout.onMeasureAfterInterface()
{
@Override
public void operation()
{
Log.i("coordinate","convertView to String :"+convertView.toString()+" x :"+convertView.getX()+" Y :"+convertView.getY()
+" bottom :"+convertView.getBottom() + " top :"+convertView.getTop());
Rect rect = new Rect();
boolean visible = convertView.getLocalVisibleRect(rect);
LogUtils.i("coordinate", " visible :"+visible +" position :"+ position+" top :"+rect.top +" left :"+rect.left
+" bottom :"+rect.bottom +" right :"+ rect.right);
mList.getRefreshableView().smoothScrollBy(measureSmoothLength(rect.bottom),500);
}
};
((MeasureViewHeightLayout)convertView).setOnMeasureAfterInterface(onMeasureAfterInterface);
}
} else
{
holder.bottom_action_bar.setVisibility(GONE);
((MeasureViewHeightLayout)convertView).setOnMeasureAfterInterface(null);
holder.statusArrorImg.setImageResource(R.mipmap.pull_bottom_operation_icon);
}
} else
{
holder.bottom_action_bar.setVisibility(GONE);
((MeasureViewHeightLayout)convertView).setOnMeasureAfterInterface(null);
holder.statusArrorImg.setImageResource(R.mipmap.pull_bottom_operation_icon);
}
}
上面的代码设计到一个自定义的View,下面会介绍它的用途,以及ListView的滚动操作.在控制ListView滚动的时候需要计算需要滚动的距离.代码如下:
/****
* /**
* 70代表:每个Item的固定显示高度(dp)
*68:代表需要显示和隐藏的那个部分View的高度(dp)
* @param bottom
* @return
*/
private int measureSmoothLength(int bottom)
{
float density = mContext.getResources().getDisplayMetrics().density;
int height = (int) ((70 + 68) * density);
int smoothHeigth = height - bottom;
return smoothHeigth;
}
1.如果仔细看看上面setBottomStatusByPosition方法,你一定看到了convertView被强制转化成MeasureViewHeightLayout这个类,这个类是我自定义的,那么你一定非常好奇,既然已经实现了上面的效果,为什么还要自定义View,多此一举呢,说到这儿,看个GIF,相信你就明白了.
当屏幕滑到最后一个item或者屏幕中显示的最后的item没有显示底部操作View,而你去点击右侧的按钮的时候,这个时候你点击右侧的展开按钮,是看不到底部的操作的部分的,这个时候,滑动ListView才可以看到底部的部分.针对这个问题,我们需要判断,我们要显示的那个Item是否完全显示,开始,我想到的是直接测量Item的高度,来判断是否完全显示,但是发现不好使,因为,当我们在setBottomStatusByPosition方法里面测量View的高度的时候,Item还没有测量完,这个时候,我们就需要自定义View.在View执行完毕onMeasure方法以后,回调回来,然后在通过getLocalVisibleRect的方法,获取Item的左上角,右下角坐标,然后得到Item的高度.然后根据View的可见高度,和我们期望的View的高度,得出该Item是否完全显示,用到的方法就是上面的measureSmoothLength().
/**
*/
public class MeasureViewHeightLayout extends LinearLayout
{
private onMeasureAfterInterface onMeasureAfterInterface;
public MeasureViewHeightLayout(Context context)
{
super(context);
}
public MeasureViewHeightLayout(Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
}
public MeasureViewHeightLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public MeasureViewHeightLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onDraw(Canvas canvas)
{
LogUtils.i("coordinate","on draw ");
super.onDraw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
LogUtils.i("coordinate","on measure ");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
LogUtils.i("coordinate","on layout ");
if(onMeasureAfterInterface != null)
{
onMeasureAfterInterface.operation();
}
super.onLayout(changed, l, t, r, b);
}
public void setOnMeasureAfterInterface(onMeasureAfterInterface onMeasureAfterInterface)
{
this.onMeasureAfterInterface = onMeasureAfterInterface;
}
public interface onMeasureAfterInterface
{
void operation();
}
}
measure是测量的意思,那么onMeasure()方法顾名思义就是用于测量视图的大小的。View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
以将视这里不做过多介绍,网上的资料非常全,这里贴出几个不错的blog:
https://my.oschina.net/xwy/blog/203198
http://blog.csdn.net/qinjuning/article/details/7110211/
http://blog.csdn.net/guolin_blog/article/details/17045157
Rect rect = new Rect();
boolean visible = convertView.getLocalVisibleRect(rect);
LogUtils.i("coordinate", " visible :"+visible +" position :"+ position+" top :"+rect.top +" left :"+rect.left
+" bottom :"+rect.bottom +" right :"+ rect.right);
coordinate通过日志可以看出来,这方法,只要是返回,View的左上角,右下角的坐标,这个坐标是相对于View本身而言的.关于View坐标的问题,这里面,也是贴出其他blog的地址,读者可以自行研究.这里没必要做出过多的拓展
http://blog.csdn.net/lvxiangan/article/details/19971509
http://www.cnblogs.com/ai-developers/p/4413585.html
1.在getView的方法里面,会通过inflate方法来初始化convertView,但是inflate的三个参数确实大有用途的,
inflate()方法一般接收两个参数,第一个参数就是要加载的布局id,第二个参数是指给该布局的外部再嵌套一层父布局,如果不需要就直接传null。这样就成功成功创建了一个布局的实例,之后再将它添加到指定的位置就可以显示出来了。
第三个参数在这必须是false,否则返回的对象就不是MeasureViewHeightLayout对象了.
convertView = layoutInflater.inflate(R.layout.file_item, parent,false);
http://blog.csdn.net/guolin_blog/article/details/12921889
2.根据ListView的复用机制,需要在不要回调的地方设置回调为null,避免多次回调operation()方法,导致ListView多次执行smoothScrollBy方法,出现错误.
smoothScrollBy(heigth, time);//平滑height,在time内(毫秒单位).