Android好奇宝宝_番外篇_看脸的世界_08

来源:互联网 发布:linux centos镜像 编辑:程序博客网 时间:2024/06/09 16:16

废话少说,先上效果图:


(左侧的图片是我用window画图软件1分钟画的,所以就不要嫌丑了,You can you up no bb.)

这是我发过最挫的效果图了,不过这是由于没有图片素材导致的,就不要在意这些细节了,知道实现原理后完全可以发挥你的想象去实现更美观的效果。


这个效果也是有开源库的,不过我又把名字给忘了,不过我记得原理,于是就试着自己写了一下。


其实原理很简单,我在另一篇博客(一个有吃豆人删除动画的ListView)也说过了,这一篇当做兑换那些年少轻狂不更事时许下的诺言(是不是瞬间文艺小清新了)。


原理:

在dispatchDraw(Canvas canvas)里于画布原点(这里指坐标(0,0),即左上角)处画一个HeadView,遮挡住原先ListView在该区域要显示的内容。

比如上面这个例子中的HeadView就长这样:(中间加上一个日期就行了,这可是我用画图软件,耗费了将近40秒画的,花了我很多精力的!所以请勿随便拿去用,起码得先点赞和评论嘛)。


声明:

接下来称那些特殊位置的item为sticky(粘性的),即左侧显示十字型背景的为sticky,显示两条竖线的为普通item。


例子:

有数据数组为:(15-03-01,A),(15-03-01,B),(15-03-02,C),(15-03-02,D),(15-03-02,E)

则位置0(15-03-01,A)和位置2(15-03-02,C)为sticky的,分别为日期15-03-01和15-03-02的起始item。


高清源码:


(1)自定义ListView

首先先自定义一个JJJListView继承ListView,写下构造方法什么的,没啥好说的。

public class JJJStickyListView extends ListView {public JJJStickyListView(Context context, AttributeSet attrs) {super(context, attrs);//设置滚动监听super.setOnScrollListener(new MyOnScrollListener());mContext = context;}}


(2)定义接口

JJJListView要把HeadView画出来,它得知道HeadView长怎么样,要画多大吧。所以定义一个接口给外部设置,JJJListView通过外部设置的接口获取HeadView的信息。

public interface StickyListener {// 提供HeadViewpublic View getHeadView();// 以dp为单位提供HeadView的宽public int getHeadViewWidthInDp();// 以dp为单位提供HeadView的高public int getHeadViewHeightInDp();// 提供那个位置的item是Sticky的判断逻辑public boolean isStickyPosition(int pos);// HeadView发生改变时对HeadView进行内容上的修改public void onHeadViewChange(View hView, int firstP, int vCount, int tCount);}

之前的看过的开源库是不要求外部传入HeadView的宽和高的,但那个开源库的HeadView是横向布满ListView的,我们不是,还是固定死宽高方便点。


(3)测量HeadView的大小

在ListView进行测量时,也对HeadView进行测量:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (mHeadView == null) {return;}// 对HeadView进行大小的测量measureChild(mHeadView, MeasureSpec.makeMeasureSpec(getHeadViewWidth(), MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(getHeadViewHeight(), MeasureSpec.EXACTLY));};

getHeadViewWidth()和getHeadViewHeight()只是简单的进行dp和px的转换,xml里我们一般会设置大小单位为dp在不同分辨率的手机上获得更好的效果,但是在代码设置中都是以px为单位的,这方面网上文章很多,讲的也很详细,我就不废话了。

private int getHeadViewWidth() {if (mListener == null) {return 0;}return (int) (mListener.getHeadViewWidthInDp() * mContext.getResources().getDisplayMetrics().density + 0.5f);}private int getHeadViewHeight() {if (mListener == null) {return 0;}return (int) (mListener.getHeadViewHeightInDp() * mContext.getResources().getDisplayMetrics().density + 0.5f);}


(4)布局HeadView的位置

同上,在ListView的布局过程中对HeadView进行布局到左上角。

protected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (mHeadView == null) {return;}// 对HeadView进行位置布局mHeadView.layout(0, 0, mHeadView.getMeasuredWidth(), mHeadView.getMeasuredHeight());};
注意这里用的是getMeasuredWidth()而不是getWidth(),前面我们自己对HeadView进行过测量,所以getMeasuredWidth()是有值的,但是getWidth()在HeadView真正被绘制出来后才有值,高度同理。

(一个View的显示流程是:测量大小-->位置布局-->绘制)


(5)绘制HeadView

protected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);drawChild(canvas, mHeadView, getDrawingTime());};

这个不用解释吧。


(6)HeadView内容的改变

经过上面的步骤,HeadView已经以确定的大小和位置被画在ListView上了。但是当ListView滚动时,要对HeadView的内容进行修改,以上面效果图的例子就是修改TextView显示的日期。


那么,首先,要知道HeadView在什么时候需要进行修改:

当ListView第一个可见的item为sticky时,需要对HeadView进行修改。


不知道为什么的请发动你们的大脑,仔细想一想。

在第一步初始化时我们已经设置了滚动监听了,现在我们在滚动时进行判断:

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {if (mHeadView == null) {if (mListener != null) {// 要先取得HeadViewmHeadView = mListener.getHeadView();}}if (mListener != null) {if (mListener.isStickyPosition(firstVisibleItem)) {// 如果第一个可见的item是Sticky的// 那么布局HeadView的位置回原点(这是因为推的效果才需要这么做)// 并且通知Listener HeadView里的内容需要更新mHeadView.layout(0, 0, mHeadView.getMeasuredWidth(), mHeadView.getMeasuredHeight());mListener.onHeadViewChange(mHeadView, firstVisibleItem, visibleItemCount, totalItemCount);}}}

调用外部设置的接口所实现的onHeadViewChange方法对HeadView进行内容更新。


(7)推的效果

当一个新的sticky的item遇到HeadView时,实现HeadView被往上推的效果。

要先知道这件事在什么条件下发生,然后计算HeadView的Y坐标,然后进行布局就可以了。


什么时候发生呢?

在第二个可见的item为sticky时发生。


不知道为什么的请再次发动你的大脑。

if (mListener.isStickyPosition(firstVisibleItem + 1)) {// 如果第二个可见的item是Sticky的// 开始推的效果,即HeadView不再显示在原点// HeadView的Bottom为第二个可见的item的Top// HeadView的Top为Bottom减去HeadView的高int headBottom = getChildAt(1).getTop();int headTop = headBottom - mHeadView.getMeasuredHeight();Log.e("headBottom", headBottom + "");Log.e("headTop", headTop + "");mHeadView.layout(0, headTop, mHeadView.getMeasuredWidth(), headBottom);mListener.onHeadViewChange(mHeadView, firstVisibleItem, visibleItemCount, totalItemCount);}



(8)外部需要设置的东西

首先是需要两个布局文件,一个是ListView的item的布局文件,一个是HeadView文件,并且HeadView要长得跟item布局的左侧完全一样。我就不贴布局文件了,想看的下载Demo看吧。

然后最重要的是实现一个StickyListener并设置给JJJStickyListView。

StickyListener stickyListener = new StickyListener() {@Overridepublic void onHeadViewChange(View hView, int firstP, int vCount, int tCount) {for (int i = firstP; i >= 0; i--) {if (isStickyPosition(i)) {TextView tvTextView = (TextView) hView.findViewById(R.id.tv_time);tvTextView.setText(datas.get(i).getTime());break;}}}@Overridepublic boolean isStickyPosition(int pos) {return datas.get(pos).isShowTime;}@Overridepublic View getHeadView() {//提供HeadViewView headerView = (ViewGroup) getLayoutInflater().inflate(R.layout.head, null);headerView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));return headerView;}@Overridepublic int getHeadViewWidthInDp() {return 100;}@Overridepublic int getHeadViewHeightInDp() {return 100;}};


几点注意:

<1>onHeadViewChange方法在HeadView的内容需要更新时被回调,至于新的内容应该是什么需要根据你的数据源来判断,还不是很明白的可以下载Demo再研究一下。


<2>isStickyPosition同样需要根据你的数据源来实现。


<3>getHeadView方法需要为HeadView设置一个LayoutParams,生成LayoutParams的参数随便,因为我们已经提供了HeadView的宽和高并用其作为参数给HeadView测量,所以LayoutParams不会影响到HeadView的大小,但在测量过程中会需要到它,如果不设置的话会报空指针异常。


注:Demo里用了一个SmartAdapter,看不懂的话参考: Android好奇宝宝_04_一个有3个功能的Adapter


Demo下载

1 0