• 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




  • 相关阅读:
    JavaScript 检测对象类型
    memorize
    高阶函数
    JavaScript 中的不完全函数
    VS C# Winform 重写进度条
    使用ECLIPSE+RED搭建RF自动化测试框架
    NOIP2018濒死记
    HDU 5977 Garden of Eden (树形dp+快速沃尔什变换FWT)
    打FFT时中发现的卡常技巧
    Dynamic Rankings || 动态/静态区间第k小(主席树)
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/6815709.html
Copyright © 2020-2023  润新知