先看实现效果,上图: 


ViewFlow是一个非常好用的,用于不确定item个数的水平滑动切换的开源项目。

可是从github上下载的ViewFlow事实上是不支持onItemClick功能的,touch事件中并没有处理click。 

那么怎样去支持onItemClick功能呢?

一、在实现前,先带着三个问题:

序号 问题
1 ViewFlow须要OnItemClickListener接口吗?

2 ListView又是怎样实现OnItemClick的呢?

3 OnItemClick又是怎样被调用的呢?


1.1、问题一

从源代码中能够看出ViewFlow是继承extends AdapterView 的。而AdapterView就是通常ListView、GridView等继承的且已经定义过OnItemClickListener了。

1.2、问题二

分析ListView源代码知道其继承extends AbsListView。而AbsListView又是继承extends AdapterView。

在AbsListView中事实上是实现了OnItemClickListener了。那么接下来的步骤仅仅要变化,仿AbsListView实现OnItemClick就可以。

1.3、问题三

分析AbsListView源代码。能够发现有个方法performItemClick方法。此方法一运行,自然就运行到了OnItemClick,不多说上源代码看:

/**
* Call the OnItemClickListener, if it is defined.
*
* @param view The view within the AdapterView that was clicked.
* @param position The position of the view in the adapter.
* @param id The row id of the item that was clicked.
* @return True if there was an assigned OnItemClickListener that was
* called, false otherwise is returned.
*/
public boolean performItemClick(View view, int position, long id) {
if (mOnItemClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
if (view != null) {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
}
mOnItemClickListener.onItemClick(this, view, position, id);
return true;
} return false;
}

那么仅仅要我们想办法在ViewFlow中运行performItemClick就OK了。


二、AbsListView是怎样运行performItemClick?

一般用onItemClick中比較重要的是方法入參的postion。那么怎样获取postion呢?

2.1、postion的获取

2.1.1 在AbsListView的onTouchEvent中,在MotionEvent.ACTION_DOWN时evnet.getX与event.getY,获取出x与y坐标,再依据pointToPosition方法计算出点击item的position下标。截取片断代码例如以下:

@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!isEnabled()) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return isClickable() || isLongClickable();
}
....
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
switch (mTouchMode) {
case TOUCH_MODE_OVERFLING: {
...
break;
} default: {
mActivePointerId = ev.getPointerId(0);
final int x = (int) ev.getX();
final int y = (int) ev.getY();
int motionPosition = pointToPosition(x, y);//计算出down的是哪个item的postion
}

2.1.2 pointToPosition方法例如以下:

/**
* Maps a point to a position in the list.
*
* @param x X in local coordinate
* @param y Y in local coordinate
* @return The position of the item which contains the specified point, or
* {@link #INVALID_POSITION} if the point does not intersect an item.
*/
public int pointToPosition(int x, int y) {
Rect frame = mTouchFrame;
if (frame == null) {//仅仅是为了避免反复new Rect
mTouchFrame = new Rect();
frame = mTouchFrame;
} final int count = getChildCount();
for (int i = count - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child.getVisibility() == View.VISIBLE) {
child.getHitRect(frame);//获取子控件在父控件坐标系中的矩形坐标
if (frame.contains(x, y)) {
return mFirstPosition + i;
}
}
}
return INVALID_POSITION;
}

2.2、PerformClick的运行

2.2.1 点击也是在touch里处理的。那么直接看onTouchEvent中怎样将点击关联运行的。

  case MotionEvent.ACTION_UP: {
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
case TOUCH_MODE_TAP:
case TOUCH_MODE_DONE_WAITING:
final int motionPosition = mMotionPosition;
final View child = getChildAt(motionPosition - mFirstPosition); ....
//构造了PerformClick内部来用于运行点击事件
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
final AbsListView.PerformClick performClick = mPerformClick;
performClick.mClickMotionPosition = motionPosition;
performClick.rememberWindowAttachCount();
.... if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
...
mLayoutMode = LAYOUT_NORMAL;
if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
....
if (mTouchModeReset != null) {
removeCallbacks(mTouchModeReset);
}
mTouchModeReset = new Runnable() {
@Override
public void run() {
mTouchMode = TOUCH_MODE_REST;
child.setPressed(false);
setPressed(false);
if (!mDataChanged) {
performClick.run();//直接运行run方法
}
}
};
postDelayed(mTouchModeReset,
ViewConfiguration.getPressedStateDuration());
} else {
mTouchMode = TOUCH_MODE_REST;
updateSelectorState();
}
return true;
} else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
performClick.run();//直接运行run方法
...

2.2.2 再看看PerformClick是怎样实现的

/**
* A base class for Runnables that will check that their view is still attached to
* the original window as when the Runnable was created.
*
*/
private class WindowRunnnable { //只用于推断当前即将要运行click时window是否是同一窗体,有没有由于异常情况新开的窗体了
private int mOriginalAttachCount; public void rememberWindowAttachCount() {
mOriginalAttachCount = getWindowAttachCount();
} public boolean sameWindow() {
return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
}
} private class PerformClick extends WindowRunnnable implements Runnable {
int mClickMotionPosition; public void run() {
// The data has changed since we posted this action in the event queue,
// bail out before bad things happen
if (mDataChanged) return; final ListAdapter adapter = mAdapter;
final int motionPosition = mClickMotionPosition;
if (adapter != null && mItemCount > 0 &&
motionPosition != INVALID_POSITION &&
motionPosition < adapter.getCount() && sameWindow()) {
final View view = getChildAt(motionPosition - mFirstPosition);
// If there is no view, something bad happened (the view scrolled off the
// screen, etc.) and we should cancel the click
if (view != null) {//performItemClick被运行,至此AbsListView实现了onItemClick了
performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
}
}
}
}

三、ViewFlow运行performItemClick?

3.1、postion的获取

ViewFlow的postion事实上和AbsListView的postion获取有点差别。由于ViewFlow是水平滑动而AbsListView是竖向的。

item会不在同一屏幕宽中。使用x与y坐标再遍历ChildView的矩形坐标系并不能适用。

那么怎样来获取postion呢?

翻看源代码有ViewFlow有个ViewSwitchListener,onSwitched中有对应的postion与View。

仅仅须要查看onSwitched在何处被调用,postion与view是怎样被斌值就可以。

private void postViewSwitched(int direction) {
if (direction == 0)
return; if (direction > 0) { // to the right
mCurrentAdapterIndex++;
mCurrentBufferIndex++;
... } else { // to the left
mCurrentAdapterIndex--;
mCurrentBufferIndex--;
...
} ...
if (mViewSwitchListener != null) { //通过在构造方法mLoadedViews(List<View>)初始化,mCurrentAdapterIndex当前显示的adapter中position位置
mViewSwitchListener
.onSwitched(mLoadedViews.get(mCurrentBufferIndex),
mCurrentAdapterIndex);
}
logBuffer();
}

3.2、PerformClick的运行

同AbsListView的PerformClick运行是同理,这里也是通过在0nTouchEvent的up中运行的。

@Override
public boolean onTouchEvent(MotionEvent ev) {
.... final int action = ev.getAction();
final float x = ev.getX(); //---------add start 获取Y坐标
final float y = ev.getY();
//---------add end switch (action) {
case MotionEvent.ACTION_DOWN:
... // Remember where the motion event started
mLastMotionX = x;
//---------add start
mLastMotionY = y;
//---------add start
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
: TOUCH_STATE_SCROLLING;
mIsClick = true; //每次down是默认是一次点击事件,在move中有x轴或y轴的偏移时则取消是click
break; case MotionEvent.ACTION_MOVE:
final int deltaX = (int) (mLastMotionX - x); boolean xMoved = Math.abs(deltaX) > mTouchSlop;
//---------add start 计算y移动偏移量 推断y轴是否有移动过及本次down下,是否是一次click事件
float tempDeltaX = mLastMotionX - ev.getX();
float tempdeltaY = mLastMotionY - ev.getY();
boolean isXMoved = Math.abs(tempDeltaX) > MOVE_TOUCHSLOP;
boolean isYMoved = Math.abs(tempdeltaY) > MOVE_TOUCHSLOP;
boolean tempIsMoved = isXMoved || isYMoved ; //xy有点偏移都觉得不是点击事件
mIsClick = !tempIsMoved; //若x与y偏移量都过小,则觉得是一次click事件
//Log.e("------->", "ACTION_MOVE tempDeltaX:"+tempDeltaX+" tempdeltaY: "+tempdeltaY+" mTouchSlop:"+mTouchSlop+" isXMoved:"+isXMoved+" isYMoved:"+isYMoved+" isClick:"+mIsClick);
//---------add start ...
break; case MotionEvent.ACTION_UP:
....
//------------------若是一次click。则运行点击。 这里仿AbsListView 採用PerformClick
//Log.e("------->", "ACTION_UP isClick:"+mIsClick);
if(mIsClick){
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
final ViewFlow.PerformClick performClick = mPerformClick;
performClick.mClickMotionPosition = mCurrentAdapterIndex;
performClick.rememberWindowAttachCount(); //记录点击时的连接窗体次数
performClick.run();
}
//------------------
..... break;
....
}
return true;
}

而PerformClick的实现例如以下:

/**
* A base class for Runnables that will check that their view is still attached to
* the original window as when the Runnable was created.
*
*/
private class WindowRunnnable {
private int mOriginalAttachCount; public void rememberWindowAttachCount() {
mOriginalAttachCount = getWindowAttachCount(); //getWindowAttachCount 获取控件绑在窗体上的次数
} public boolean sameWindow() { //推断是否是同一个窗体,异常情况时界面attachWindowCount会是+1,那么此时就不是同一个窗体了。
return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
}
} private class PerformClick extends WindowRunnnable implements Runnable {
int mClickMotionPosition; public void run() {
// The data has changed since we posted this action in the event queue,
// bail out before bad things happen
//if (mDataChanged) return; final Adapter adapter = mAdapter;
final int motionPosition = mClickMotionPosition;
if (adapter != null && mAdapter.getCount() > 0 &&
motionPosition != INVALID_POSITION &&
motionPosition < adapter.getCount() && sameWindow()) {
//final View view = getChildAt(motionPosition - mFirstPosition); // mFirstPosition不关注
final View view = mLoadedViews.get(mCurrentBufferIndex); //position及view的获取借鉴onSwitched方法
// If there is no view, something bad happened (the view scrolled off the
// screen, etc.) and we should cancel the click
if (view != null) {
performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
}
}
}
}

至此搞定了ViewFlow的onItemClick了。


最后上个ViewFlow的使用演示样例Demo

public class CircleViewFlowExample extends Activity {

	private ViewFlow viewFlow;

	/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.circle_title);
setContentView(R.layout.circle_layout); viewFlow = (ViewFlow) findViewById(R.id.viewflow);
viewFlow.setOnItemClickListener(new OnItemClickListener() { @Override
public void onItemClick(AdapterView<? > parent, View view,
int position, long id) {
Toast.makeText(CircleViewFlowExample.this, "CircleViewFlowExample点击了position:"+position+"的图片", 1).show();
Log.e("-----", "CircleViewFlowExample点击了position:"+position+"的图片");
}
});
viewFlow.setAdapter(new ImageAdapter(this), 5);
CircleFlowIndicator indic = (CircleFlowIndicator) findViewById(R.id.viewflowindic);
viewFlow.setFlowIndicator(indic);
Log.e("-----", "CircleViewFlowExample onCreate");
}
/* If your min SDK version is < 8 you need to trigger the onConfigurationChanged in ViewFlow manually, like this */
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
viewFlow.onConfigurationChanged(newConfig);
}
}

总结


先吐槽一两句:CSDN的markdown编辑的时候感觉好爽,但最后保存公布时。老是出问题。

不是timeout,就是服务异常,公布不了。

保存在草稿箱中也能够正常预览。就是不能正常公布。

无奈仅仅好转成普通模式。代码都一块一块贴进来。郁闷....


github上ViewFlow下载地址:

https://github.com/pakerfeldt/android-viewflow

完整源代码demo下载(支持onItemClick的ViewFlow)

http://download.csdn.net/detail/chenshufei2/9003119



ViewFlow增强onItemClick功能及ViewFlow AbsListView源代码分析的更多相关文章

  1. C#中关于增强类功能的几种方式

    C#中关于增强类功能的几种方式 本文主要讲解如何利用C#语言自身的特性来对一个类的功能进行丰富与增强,便于拓展现有项目的一些功能. 拓展方法 扩展方法被定义为静态方法,通过实例方法语法进行调用.方法的 ...

  2. C# 中一些类关系的判定方法 C#中关于增强类功能的几种方式 Asp.Net Core 轻松学-多线程之取消令牌

    1.  IsAssignableFrom实例方法 判断一个类或者接口是否继承自另一个指定的类或者接口. public interface IAnimal { } public interface ID ...

  3. Android源代码分析之拍照、图片、录音、视频和音频功能

    Android源代码分析之拍照.图片.录音.视频和音频功能   //选择图片 requestCode 返回的标识 Intent innerIntent = new Intent(Intent.ACTI ...

  4. Hadoop源代码分析

    http://wenku.baidu.com/link?url=R-QoZXhc918qoO0BX6eXI9_uPU75whF62vFFUBIR-7c5XAYUVxDRX5Rs6QZR9hrBnUdM ...

  5. Android万能适配器base-adapter-helper的源代码分析

    项目地址:https://github.com/JoanZapata/base-adapter-helper 1. 功能介绍 1.1. base-adapter-helper base-adapter ...

  6. Hadoop源代码分析(完整版)

    Hadoop源代码分析(一) 关键字: 分布式云计算 Google的核心竞争技术是它的计算平台.Google的大牛们用了下面5篇文章,介绍了它们的计算设施. GoogleCluster:http:// ...

  7. android-plugmgr源代码分析

    android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...

  8. 转:SDL2源代码分析

    1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...

  9. 转:RTMPDump源代码分析

    0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...

随机推荐

  1. 九度oj 1547

    题目描述: 给定一个初始为空的栈,和n个操作组成的操作序列,每个操作只可能是出栈或者入栈. 要求在操作序列的执行过程中不会出现非法的操作,即不会在空栈时执行出栈操作,同时保证当操作序列完成后,栈恰好为 ...

  2. cf468C Hack it!

    Little X has met the following problem recently. Let's define f(x) as the sum of digits in decimal r ...

  3. 页面中用Context.Handler传递

       最近被WCF弄得身心疲惫.今天抽空看了一下页面传值的一些技巧.传统的cookie session 什么的就不介绍了 今天介绍Context的用法 首先要应用using System.Runtim ...

  4. 济南学习 Day 5 T1 晚

    取模(mod) [题目描述] 有一个整数a和n个整数b_1, …, b_n.在这些数中选出若干个数并重新排列,得到c_1,…, c_r.我们想保证a mod c_1 mod c_2 mod … mod ...

  5. CSS参数介绍

    原文发布时间为:2008-08-03 -- 来源于本人的百度文章 [由搬家工具导入] 行高       line-height: 16px 宽度       (具体位置)-width: 16px 文字 ...

  6. XPath用法详解

    1.XPath是什么 XPath 是一门在 XML 文档中查找信息的语言.XPath 用于在 XML 文档中通过元素和属性进行导航(你可以理解为一种类似正则表达式的方法) 2.XPath的语法 表达式 ...

  7. 【Tomcat】使用tomcat manager 管理和部署项目,本地部署项目到服务器

    在部署tomcat项目的时候,除了把war文件直接拷贝到tomcat的webapp目录下,还有一种方法可以浏览器中管理和部署项目,那就是使用tomcat manager. 默认情况下,tomcat m ...

  8. Codeforces Round #265 (Div. 2) C 暴力+ 找规律+ 贪心

    C. No to Palindromes! time limit per test 1 second memory limit per test 256 megabytes input standar ...

  9. 关于FileZilla Server的安装问题

    网上很多FileZilla Server教程到最后一步在本机上测试访问成功就没了,实际上还是不完整的,一般情况下外网还是访问不了,外网访问同样很重要. 可能有点童鞋会说安装的时候防火墙提示的时候我也点 ...

  10. 死磕 java同步系列之自己动手写一个锁Lock

    问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...