Android UI 优化之 AbsListView深度优化

android 提供的很多List控件如 listview、gridview 默认都会显示一个_fadingEdge_的东西,它在View的top和bottom处各显示一个渐变半透的阴影以达到更好的视觉效果,但是这个带来的副作 用就是导致在性能不是那么强劲的机器上,一些listview,gridview的拖动会显得很不流畅,因为我们知道绘制带Alpha的图片是最耗时 的。 

 

我们的优化思路就是对这个_fadingEdge_做一些修改,当view处于滚动状态时,通过接口setVerticalFadingEdgeEnabled(false)让其不显示fadingedge,当view处于静止状态时,通过接口setVerticalFadingEdgeEnabled(true)恢复显示fadingedge。以上的listview和gridview等控件都是继承与AbsListView,所以我们直接修改framework中的AbsListView.java文件,就可以达到系统级的改动效果了。 

具体修改如下: 

@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(); 
        } 
        if (mFastScroller != null) { 
            boolean intercepted = mFastScroller.onTouchEvent(ev); 
            if (intercepted) { 
                return true; 
            } 
        } 
        final int action = ev.getAction(); 
        View v; 
        int deltaY; 
        if (mVelocityTracker == null) { 
            mVelocityTracker = VelocityTracker.obtain(); 
        } 
        mVelocityTracker.addMovement(ev); 
        switch (action & MotionEvent.ACTION_MASK) { 
        case MotionEvent.ACTION_DOWN: { 
            setVerticalFadingEdgeEnabled(false); 
            mActivePointerId = ev.getPointerId(0); 
            final int x = (int) ev.getX(); 
            final int y = (int) ev.getY(); 
            int motionPosition = pointToPosition(x, y); 
            if (!mDataChanged) { 
                if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0) 
                        && (getAdapter().isEnabled(motionPosition))) { 
                    // User clicked on an actual view (and was not stopping a fling). It might be     // a 
                    // click or a scroll. Assume it is a click until proven otherwise 
                    mTouchMode = TOUCH_MODE_DOWN; 
                    // FIXME Debounce 
                    if (mPendingCheckForTap == null) { 
                        mPendingCheckForTap = new CheckForTap(); 
                    } 
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 
                } else { 
                    if (ev.getEdgeFlags() != 0 && motionPosition < 0) { 
                        // If we couldn't find a view to click on, but the down event was // touching 
                        // the edge, we will bail out and try again. This allows the edge // correcting 
                        // code in ViewRoot to try to find a nearby view to select 
                        return false; 
                    } 
                    if (mTouchMode == TOUCH_MODE_FLING) { 
                        // Stopped a fling. It is a scroll. 
                        createScrollingCache(); 
                        mTouchMode = TOUCH_MODE_SCROLL; 
                        mMotionCorrection = 0; 
                        motionPosition = findMotionRow(y); 
                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); 
                    } 
                } 
            } 
            if (motionPosition >= 0) { 
                // Remember where the motion event started 
                v = getChildAt(motionPosition - mFirstPosition); 
                mMotionViewOriginalTop = v.getTop(); 
            } 
            mMotionX = x; 
            mMotionY = y; 
            mMotionPosition = motionPosition; 
            mLastY = Integer.MIN_VALUE; 
            break; 
        } 
        case MotionEvent.ACTION_MOVE: { 
            final int pointerIndex = ev.findPointerIndex(mActivePointerId); 
            final int y = (int) ev.getY(pointerIndex); 
            deltaY = y - mMotionY; 
            switch (mTouchMode) { 
            case TOUCH_MODE_DOWN: 
            case TOUCH_MODE_TAP: 
            case TOUCH_MODE_DONE_WAITING: 
                // Check if we have moved far enough that it looks more like a 
                // scroll than a tap 
                startScrollIfNeeded(deltaY); 
                break; 
            case TOUCH_MODE_SCROLL: 
                if (PROFILE_SCROLLING) { 
                    if (!mScrollProfilingStarted) { 
                        Debug.startMethodTracing("AbsListViewScroll"); 
                        mScrollProfilingStarted = true; 
                    } 
                } 
                if (y != mLastY) { 
                    deltaY -= mMotionCorrection; 
                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; 
                      
                    // No need to do all this work if we're not going to move anyway 
                    boolean atEdge = false; 
                    if (incrementalDeltaY != 0) { 
                        atEdge = trackMotionScroll(deltaY, incrementalDeltaY); 
                    } 
                    // Check to see if we have bumped into the scroll limit 
                    if (atEdge && getChildCount() > 0) { 
                        // Treat this like we're starting a new scroll from the current 
                        // position. This will let the user start scrolling back into 
                        // content immediately rather than needing to scroll back to the 
                        // point where they hit the limit first. 
                        int motionPosition = findMotionRow(y); 
                        if (motionPosition >= 0) { 
                            final View motionView = getChildAt(motionPosition - mFirstPosition); 
                            mMotionViewOriginalTop = motionView.getTop(); 
                        } 
                        mMotionY = y; 
                        mMotionPosition = motionPosition; 
                        invalidate(); 
                    } 
                    mLastY = y; 
                } 
                break; 
            } 
            break; 
        } 
        case MotionEvent.ACTION_UP: { 
            switch (mTouchMode) { 
            case TOUCH_MODE_DOWN: 
            case TOUCH_MODE_TAP: 
            case TOUCH_MODE_DONE_WAITING: 
                setVerticalFadingEdgeEnabled(true); 
                final int motionPosition = mMotionPosition; 
                final View child = getChildAt(motionPosition - mFirstPosition); 
                if (child != null && !child.hasFocusable()) { 
                    if (mTouchMode != TOUCH_MODE_DOWN) { 
                        child.setPressed(false); 
                    } 
                    if (mPerformClick == null) { 
                        mPerformClick = new PerformClick(); 
                    } 
                    final AbsListView.PerformClick performClick = mPerformClick; 
                    performClick.mChild = child; 
                    performClick.mClickMotionPosition = motionPosition; 
                    performClick.rememberWindowAttachCount(); 
                    mResurrectToPosition = motionPosition; 
                    if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { 
                        final Handler handler = getHandler(); 
                        if (handler != null) { 
                            handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? 
                                    mPendingCheckForTap : mPendingCheckForLongPress); 
                        } 
                        mLayoutMode = LAYOUT_NORMAL; 
                        if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { 
                            mTouchMode = TOUCH_MODE_TAP; 
                            setSelectedPositionInt(mMotionPosition); 
                            layoutChildren(); 
                            child.setPressed(true); 
                            positionSelector(child); 
                            setPressed(true); 
                            if (mSelector != null) { 
                                Drawable d = mSelector.getCurrent(); 
                                if (d != null && d instanceof TransitionDrawable) { 
                                    ((TransitionDrawable) d).resetTransition(); 
                                } 
                            } 
                            postDelayed(new Runnable() { 
                                public void run() { 
                                    child.setPressed(false); 
                                    setPressed(false); 
                                    if (!mDataChanged) { 
                                        post(performClick); 
                                    } 
                                    mTouchMode = TOUCH_MODE_REST; 
                                } 
                            }, ViewConfiguration.getPressedStateDuration()); 
                        } else { 
                            mTouchMode = TOUCH_MODE_REST; 
                        } 
                        return true; 
                    } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { 
                        post(performClick); 
                    } 
                } 
                mTouchMode = TOUCH_MODE_REST; 
                break; 
            case TOUCH_MODE_SCROLL: 
                final int childCount = getChildCount(); 
                if (childCount > 0) { 
                    if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top && 
                            mFirstPosition + childCount < mItemCount && 
                            getChildAt(childCount - 1).getBottom() <= 
                                    getHeight() - mListPadding.bottom) { 
                        mTouchMode = TOUCH_MODE_REST; 
                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 
                        setVerticalFadingEdgeEnabled(true); 
                    } else { 
                        final VelocityTracker velocityTracker = mVelocityTracker; 
                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 
                        final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 
      
                        if (Math.abs(initialVelocity) > mMinimumVelocity) { 
                            if (mFlingRunnable == null) { 
                                mFlingRunnable = new FlingRunnable(); 
                            } 
                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); 
                              
                            mFlingRunnable.start(-initialVelocity); 
                        } else { 
                            mTouchMode = TOUCH_MODE_REST; 
                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 
                            setVerticalFadingEdgeEnabled(true); 
                        } 
                    } 
                } else { 
                    mTouchMode = TOUCH_MODE_REST; 
                    reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 
                    setVerticalFadingEdgeEnabled(true); 
                } 
                break; 
            } 
            setPressed(false); 
            // Need to redraw since we probably aren't drawing the selector anymore 
            invalidate(); 
            final Handler handler = getHandler(); 
            if (handler != null) { 
                handler.removeCallbacks(mPendingCheckForLongPress); 
            } 
            if (mVelocityTracker != null) { 
                mVelocityTracker.recycle(); 
                mVelocityTracker = null; 
            } 
              
            mActivePointerId = INVALID_POINTER; 
            if (PROFILE_SCROLLING) { 
                if (mScrollProfilingStarted) { 
                    Debug.stopMethodTracing(); 
                    mScrollProfilingStarted = false; 
                } 
            } 
            break; 
        } 
        case MotionEvent.ACTION_CANCEL: { 
            mTouchMode = TOUCH_MODE_REST; 
            setPressed(false); 
            View motionView = this.getChildAt(mMotionPosition - mFirstPosition); 
            if (motionView != null) { 
                motionView.setPressed(false); 
            } 
            clearScrollingCache(); 
            final Handler handler = getHandler(); 
            if (handler != null) { 
                handler.removeCallbacks(mPendingCheckForLongPress); 
            } 
            if (mVelocityTracker != null) { 
                mVelocityTracker.recycle(); 
                mVelocityTracker = null; 
            } 
              
            mActivePointerId = INVALID_POINTER; 
            break; 
        } 
          
        case MotionEvent.ACTION_POINTER_UP: { 
            onSecondaryPointerUp(ev); 
            final int x = mMotionX; 
            final int y = mMotionY; 
            final int motionPosition = pointToPosition(x, y); 
            if (motionPosition >= 0) { 
                // Remember where the motion event started 
                v = getChildAt(motionPosition - mFirstPosition); 
                mMotionViewOriginalTop = v.getTop(); 
                mMotionPosition = motionPosition; 
            } 
            mLastY = y; 
            break; 
        } 
        } 
        return true; 
    } 
======================================================================== 
    private class FlingRunnable implements Runnable { 
          
        private final Scroller mScroller; 
          
        private int mLastFlingY; 
        FlingRunnable() { 
            mScroller = new Scroller(getContext()); 
        } 
        void start(int initialVelocity) { 
            int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0; 
            mLastFlingY = initialY; 
            mScroller.fling(0, initialY, 0, initialVelocity, 
                    0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE); 
            mTouchMode = TOUCH_MODE_FLING; 
            post(this); 
            if (PROFILE_FLINGING) { 
                if (!mFlingProfilingStarted) { 
                    Debug.startMethodTracing("AbsListViewFling"); 
                    mFlingProfilingStarted = true; 
                } 
            } 
        } 
        void startScroll(int distance, int duration) { 
            int initialY = distance < 0 ? Integer.MAX_VALUE : 0; 
            mLastFlingY = initialY; 
            mScroller.startScroll(0, initialY, 0, distance, duration); 
            mTouchMode = TOUCH_MODE_FLING; 
            post(this); 
        } 
        private void endFling() { 
            mTouchMode = TOUCH_MODE_REST; 
            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 
            clearScrollingCache(); 
            removeCallbacks(this); 
            if (mPositionScroller != null) { 
                removeCallbacks(mPositionScroller); 
            } 
        } 
        public void run() { 
            switch (mTouchMode) { 
            default: 
                return; 
                  
            case TOUCH_MODE_FLING: { 
                if (mItemCount == 0 || getChildCount() == 0) { 
                    endFling(); 
                    return; 
                } 
                final Scroller scroller = mScroller; 
                boolean more = scroller.computeScrollOffset(); 
                final int y = scroller.getCurrY(); 
                // Flip sign to convert finger direction to list items direction 
                // (e.g. finger moving down means list is moving towards the top) 
                int delta = mLastFlingY - y; 
                // Pretend that each frame of a fling scroll is a touch scroll 
                if (delta > 0) { 
                    // List is moving towards the top. Use first view as mMotionPosition 
                    mMotionPosition = mFirstPosition; 
                    final View firstView = getChildAt(0); 
                    mMotionViewOriginalTop = firstView.getTop(); 
                    // Don't fling more than 1 screen 
                    delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta); 
                } else { 
                    // List is moving towards the bottom. Use last view as mMotionPosition 
                    int offsetToLast = getChildCount() - 1; 
                    mMotionPosition = mFirstPosition + offsetToLast; 
                    final View lastView = getChildAt(offsetToLast); 
                    mMotionViewOriginalTop = lastView.getTop(); 
                    // Don't fling more than 1 screen 
                    delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta); 
                } 
                final boolean atEnd = trackMotionScroll(delta, delta); 
                if (more && !atEnd) { 
                    invalidate(); 
                    mLastFlingY = y; 
                    post(this); 
                } else { 
                    endFling(); 
                    AbsListView.this.setVerticalFadingEdgeEnabled(true); 
                    if (PROFILE_FLINGING) { 
                        if (mFlingProfilingStarted) { 
                            Debug.stopMethodTracing(); 
                            mFlingProfilingStarted = false; 
                        } 
                    } 
                } 
                break; 
            } 
            } 
        } 
    } 

修改后重新编译,在性能稍差的机器上运行,滚动一下listview或者gridview,你就可以看到比较明显的效果了! 

PS:但是改Framework有个屁用啊!!!!