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

废话少说,先上效果图:

(左侧的图片是我用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 {
		// 提供HeadView
		public 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) {
					// 要先取得HeadView
					mHeadView = 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() {

		@Override
		public 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;
				}
			}
		}

		@Override
		public boolean isStickyPosition(int pos) {
			return datas.get(pos).isShowTime;
		}

		@Override
		public View getHeadView() {
			//提供HeadView
			View headerView = (ViewGroup) getLayoutInflater().inflate(R.layout.head, null);
			headerView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
			return headerView;
		}

		@Override
		public int getHeadViewWidthInDp() {
			return 100;
		}

		@Override
		public 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下载

时间: 2024-11-08 10:44:49

Android好奇宝宝_番外篇_看脸的世界_08的相关文章

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

上一篇番外篇讲了一个炒鸡炒鸡简单的自定义ProgressBar,这一篇基于上一篇的基础扩展为SeekBar,没看过上一篇的,请先看一遍:传送门 先上效果图(2G内存的机子运行模拟器,所以有点卡): 这个效果之前不知道在哪里看到过,我也忘了. 下面进入正题: 测量大小和绘制部分沿用上一篇ProgressBar的,不清楚的请走上面的传送门. 对比上一篇的扩展: (1)SeekBar能通过触摸改变刻度 (2)SeekBar上方添加一个显示当前刻度的浮动View(后面用FloatView表示) (1)通

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

简单实现波纹效果 其实这一篇的效果实现很简单,写这篇博客重点是为了说另一件事,剧透一下:有关内存泄露的. 先说下效果的实现: 原理: 原理只有一个,就是Shader的使用.Shader我看别人翻译成着色器,其实它的作用就是为画笔增加颜色的渐变,画笔默认是一个颜色画到底,但是使用Shader可以实现从一个颜色渐变到另一个颜色. 想了解更多关于Shader的姿势,推荐博客:传送门 有了Shader,就能很简单的画出波纹的效果了,至于动画效果,只是动态改变画的大小而已. 高清源码: (1)初始化 重写

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

一个有吃豆人删除动画的ListView 这是一个无聊的效果,由一个无聊的程序猿,在无聊的情况下写的. 虽然这效果不中看中用,不过就当学习了. 先上图 效果一目了然,主要是: (1)移除item时执行吃豆人动画 (2)滚动时吃豆人也相应移动 (3)应对可见与不可见状态间的切换 简单原理分析: (1)吃豆人.豆.和左边的白色矩形(当然所有颜色都是可以改的,你想换成图片也行)都是用canvas画出来的. (2)问:canvas那里来的?答:ListView的canvas.具体是重写ListView的这

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

无聊刷帖看到一个求助,试着写了一下. 一个自定义Switch控件,附带动画效果. 说是控件,其实是一个布局容器,先上效果图: 先讲原理,再看高清源码. 原理: 好像没啥原理,汗... 跟其它自定义容器控件一样,一般要注意: (1)计算好大小,宽度和高度 (2)计算好子View的布局位置 不是一般要注意的: (3)动画是用的nineoldandroids (4)遮挡效果是通过控制子View的绘制顺序 高清源码: (1)计算大小: protected void onMeasure(int width

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

这一篇是记录一下本猿自定义View的一般思路,通过一个炒鸡简单的自定义ProgressBar讲解一些自定义View的基础知识.适合新手,高手勿喷,有好的指点和想法的欢迎评论. (1)确认需求 写一个自定义View,首先你要确定需求是什么.一般包括外观,事件处理,动画效果. 外观需求:ProgressBar的外观需求非常简单,就是两个矩形(当然也可以是其它形状,这里我们只实现基础的矩形)重叠显示.其中一个固定大小的当背景,一个可变宽度的来显示刻度. 事件处理:ProgressBar不是SeekBa

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

废话少说,先上图: (请看底部的4个点) 忘记是在那个APP上看到ViewPager底部的圆点指示器可以随着滚动而滚动的效果,便开始思考要怎么实现,最终发现效果实现很简单,拿来练手自定义View挺不错的. 写码之前: 写代码之前必须至少先有大概的思路,而且不要想到一点就开始写,必须对整体都大概心里有数再开始写.比如在实现这个效果时,刚开始我是想着重写线性布局,然后动态添加圆点,通过margin控制间隔.但是我发现这种办法在滚动时的处理逻辑编写起来比较复杂,既然只是几个圆点而已,直接继承View用

步步为营_Android开发课_番外篇[5]_软件的安装与卸载源码

Focus on technology, enjoy life!-- 杨焕州 QQ:804212028 原文链接:http://blog.csdn.net/y18334702058/article/details/44624305 本文可能存在参考或借助部分外界资源,如有任何侵权行为,请与我联系! 主题:软件的安装与卸载源码 从SDcard安装软件: String fileName = Environment.getExternalStorageDirectory() + "/myApp.apk

老子道德经全文_番外篇

道德经 <道德经>全文 [第一章]道可道,非常道:名可名,非常名.无名天地之始,有名万物之母.故常无欲,以观其妙:常 有欲,以观其徼(jiào).此两者同出而异名,同谓之玄,玄之又玄,众妙之门.[译文] [第二章]天下皆知美之为美,斯恶(è)已:皆知善之为善,斯不善已.故有无相生,难易相成,长短相 较,高下相倾,音声相和(hè),前后相随.是以圣人处无为之事,行不言之教,万物作焉而不辞,生而 不有,为而不恃,功成而弗居.夫(fú)唯弗居,是以不去. [译文] [第三章]不尚贤,使民不争:不贵难

H5学习_番外篇_PHP数据库操作

1. 文件操作 1.1 打开关闭文件 fopen() resource fopen ( string filename, string mode [, bool use_include_path [, resource zcontext]] )? fopen()函数将resource绑定到一个流或句柄.绑定之后.脚本就能够通过句柄与此资源交互; 例1:以仅仅读方式打开一个位于本地server的文本文件 $fh = fopen("test.txt", "r"); 例2