ViewFlow增强onItemClick功能及ViewFlow AbsListView源码分析

先看实现效果,上图:



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

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-02 07:22:40

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

ViewFlow增强onItemClick功能及ViewFlow AbsListView源代码分析

先看实现效果,上图: ViewFlow是一个非常好用的,用于不确定item个数的水平滑动切换的开源项目. 可是从github上下载的ViewFlow事实上是不支持onItemClick功能的,touch事件中并没有处理click. 那么怎样去支持onItemClick功能呢? 一.在实现前,先带着三个问题: 序号 问题 1 ViewFlow须要OnItemClickListener接口吗? 2 ListView又是怎样实现OnItemClick的呢? 3 OnItemClick又是怎样被调用的呢

开源中国 OsChina Android 客户端源码分析(9)下载APK功能

源码中用以下载客户端的类为DownloadService,是一个服务.如果你对android服务不够理解的话,建议先查阅下有关服务的知识点.源码分析如下: 1首先我们先来看下该服务中几个重写的方法: 1.1onCreate()中 首先声明了自定义的绑定器对象,并在自定义的绑定器中添加了几个界面可以访问服务的方法,我们发现在这几个方法中,目前实际用到的start()方法用以开始下载APK,其他的没有用到.获取通知管理器.设置服务为 非前台服务.代码注释中,火蚁表明了不确定性. 其实如果将服务设置为

ABP源码分析二十六:核心框架中的一些其他功能

本文是ABP核心项目源码分析的最后一篇,介绍一些前面遗漏的功能 AbpSession AbpSession: 目前这个和CLR的Session没有什么直接的联系.当然可以自定义的去实现IAbpSession使之与CLR的Session关联 IAbpSession:定义如下图中的四个属性. NullAbpSession:IAbpSession的一个缺省实现,给每个属性都给予null值,无实际作用 ClaimsAbpSession:实现了从ClaimsPrincipal/ClaimsIdentity

List集合基础增强底层源码分析

List集合基础增强底层源码分析 作者:Stanley 罗昊 集合分为三个系列,分别为:List.set.map List系列 特点:元素有序可重复 有序指的是元素的添加顺序,也就是说,元素被第一个存进去的时候,它就在第一位,这就是list集合的元素添加顺序: 通常情况下我们所说的有序有两个概念,第一个是添加顺序,第二个是大小顺序(实际上就是元素值的大小) List下面重点关注两个实现类分别是: ArrayList LinkedList ArrayList ArrayList底层实现是数组,既然

JVM源码分析之警惕存在内存泄漏风险的FinalReference(增强版)

概述 JAVA对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了四种其他引用:SoftReference.WeakReference.PhantomReference.FinalReference,本文主要想讲的是FinalReference,因为我们在使用内存分析工具比如mat等在分析一些oom的heap的时候,经常能看到 java.lang.ref.Finalizer占用的内存大小远远排在前面(其实通过jmap -histo就能发现,如下图所示),而这个类占用的内存大小又

nginx源码分析--nginx模块解析

nginx的模块非常之多,可以认为所有代码都是以模块的形式组织,这包括核心模块和功能模块,针对不同的应用场合,并非所有的功能模块都要被用到,附录A给出的是默认configure(即简单的http服务器应用)下被连接的模块,这里虽说是模块连接,但nginx不会像apache或lighttpd那样在编译时生成so动态库而在程序执行时再进行动态加载,nginx模块源文件会在生成nginx时就直接被编译到其二进制执行文件中,所以如果要选用不同的功能模块,必须对nginx做重新配置和编译.对于功能模块的选

SwipeRefreshLayout 源码分析

## SwipeRefreshLayout 源码分析 > 本文基于 v4 版本 `23.2.0` extends `ViewGroup` implements `NestedScrollingParent` `NestedScrollingChild` ``` java.lang.Object ? android.view.View ? android.view.ViewGroup ? android.support.v4.widget.SwipeRefreshLayout ``` SwipeR

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

项目地址:https://github.com/JoanZapata/base-adapter-helper 1. 功能介绍 1.1. base-adapter-helper base-adapter-helper 是对传统的 BaseAdapter ViewHolder 模式的一个封装.主要功能就是简化我们书写 AbsListView 的 Adapter 的代码,如 ListView,GridView. 1.2 基本使用 mListView.setAdapter(mAdapter = new

PreferenceActivity源码分析与简单应用

· PreferenceActivity可以显示一系列Header,每一个Header可以关联一个Fragment或者Activity.此外,它还可以直接显示Preference条目. · PreferenceActivity显示Header的时候有两种模式:single pane和two panes:如果是Fragment,那么在two panes模式下,也就是大屏模式下,它可以同时显示Header和Fragment,这充分利用了屏幕的空间.而在singlepane模式下只会显示Header,