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文件,就可以达到系统级的改动效果了。
具体修改如下:
我们的优化思路就是对这个fadingedge做一些修改,当view处于滚动状态时,通过接口setVerticalFadingEdgeEnabled(false)让其不显示fadingedge,当view处于静止状态时,通过接口setVerticalFadingEdgeEnabled(true)恢复显示fadingedge。以上的listview和gridview等控件都是继承与AbsListView,所以我们直接修改framework中的AbsListView.java文件,就可以达到系统级的改动效果了。
具体修改如下:
001 | @Override |
002 | public boolean onTouchEvent(MotionEvent ev) { |
003 | if (!isEnabled()) { |
004 | // A disabled view that is clickable still consumes the touch |
005 | // events, it just doesn't respond to them. |
006 | return isClickable() || isLongClickable(); |
007 | } |
008 | if (mFastScroller != null ) { |
009 | boolean intercepted = mFastScroller.onTouchEvent(ev); |
010 | if (intercepted) { |
011 | return true ; |
012 | } |
013 | } |
014 | final int action = ev.getAction(); |
015 | View v; |
016 | int deltaY; |
017 | if (mVelocityTracker == null ) { |
018 | mVelocityTracker = VelocityTracker.obtain(); |
019 | } |
020 | mVelocityTracker.addMovement(ev); |
021 | switch (action & MotionEvent.ACTION_MASK) { |
022 | case MotionEvent.ACTION_DOWN: { |
023 | setVerticalFadingEdgeEnabled( false ); |
024 | mActivePointerId = ev.getPointerId( 0 ); |
025 | final int x = ( int ) ev.getX(); |
026 | final int y = ( int ) ev.getY(); |
027 | int motionPosition = pointToPosition(x, y); |
028 | if (!mDataChanged) { |
029 | if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0 ) |
030 | && (getAdapter().isEnabled(motionPosition))) { |
031 | // User clicked on an actual view (and was not stopping a fling). It might be a |
032 | // click or a scroll. Assume it is a click until proven otherwise |
033 | mTouchMode = TOUCH_MODE_DOWN; |
034 | // FIXME Debounce |
035 | if (mPendingCheckForTap == null ) { |
036 | mPendingCheckForTap = new CheckForTap(); |
037 | } |
038 | postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); |
039 | } else { |
040 | if (ev.getEdgeFlags() != 0 && motionPosition < 0 ) { |
041 | // If we couldn't find a view to click on, but the down event was touching |
042 | // the edge, we will bail out and try again. This allows the edge correcting |
043 | // code in ViewRoot to try to find a nearby view to select |
044 | return false ; |
045 | } |
046 | if (mTouchMode == TOUCH_MODE_FLING) { |
047 | // Stopped a fling. It is a scroll. |
048 | createScrollingCache(); |
049 | mTouchMode = TOUCH_MODE_SCROLL; |
050 | mMotionCorrection = 0 ; |
051 | motionPosition = findMotionRow(y); |
052 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); |
053 | } |
054 | } |
055 | } |
056 | if (motionPosition >= 0 ) { |
057 | // Remember where the motion event started |
058 | v = getChildAt(motionPosition - mFirstPosition); |
059 | mMotionViewOriginalTop = v.getTop(); |
060 | } |
061 | mMotionX = x; |
062 | mMotionY = y; |
063 | mMotionPosition = motionPosition; |
064 | mLastY = Integer.MIN_VALUE; |
065 | break ; |
066 | } |
067 | case MotionEvent.ACTION_MOVE: { |
068 | final int pointerIndex = ev.findPointerIndex(mActivePointerId); |
069 | final int y = ( int ) ev.getY(pointerIndex); |
070 | deltaY = y - mMotionY; |
071 | switch (mTouchMode) { |
072 | case TOUCH_MODE_DOWN: |
073 | case TOUCH_MODE_TAP: |
074 | case TOUCH_MODE_DONE_WAITING: |
075 | // Check if we have moved far enough that it looks more like a |
076 | // scroll than a tap |
077 | startScrollIfNeeded(deltaY); |
078 | break ; |
079 | case TOUCH_MODE_SCROLL: |
080 | if (PROFILE_SCROLLING) { |
081 | if (!mScrollProfilingStarted) { |
082 | Debug.startMethodTracing( "AbsListViewScroll" ); |
083 | mScrollProfilingStarted = true ; |
084 | } |
085 | } |
086 | if (y != mLastY) { |
087 | deltaY -= mMotionCorrection; |
088 | int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; |
089 | |
090 | // No need to do all this work if we're not going to move anyway |
091 | boolean atEdge = false ; |
092 | if (incrementalDeltaY != 0 ) { |
093 | atEdge = trackMotionScroll(deltaY, incrementalDeltaY); |
094 | } |
095 | // Check to see if we have bumped into the scroll limit |
096 | if (atEdge && getChildCount() > 0 ) { |
097 | // Treat this like we're starting a new scroll from the current |
098 | // position. This will let the user start scrolling back into |
099 | // content immediately rather than needing to scroll back to the |
100 | // point where they hit the limit first. |
101 | int motionPosition = findMotionRow(y); |
102 | if (motionPosition >= 0 ) { |
103 | final View motionView = getChildAt(motionPosition - mFirstPosition); |
104 | mMotionViewOriginalTop = motionView.getTop(); |
105 | } |
106 | mMotionY = y; |
107 | mMotionPosition = motionPosition; |
108 | invalidate(); |
109 | } |
110 | mLastY = y; |
111 | } |
112 | break ; |
113 | } |
114 | break ; |
115 | } |
116 | case MotionEvent.ACTION_UP: { |
117 | switch (mTouchMode) { |
118 | case TOUCH_MODE_DOWN: |
119 | case TOUCH_MODE_TAP: |
120 | case TOUCH_MODE_DONE_WAITING: |
121 | setVerticalFadingEdgeEnabled( true ); |
122 | final int motionPosition = mMotionPosition; |
123 | final View child = getChildAt(motionPosition - mFirstPosition); |
124 | if (child != null && !child.hasFocusable()) { |
125 | if (mTouchMode != TOUCH_MODE_DOWN) { |
126 | child.setPressed( false ); |
127 | } |
128 | if (mPerformClick == null ) { |
129 | mPerformClick = new PerformClick(); |
130 | } |
131 | final AbsListView.PerformClick performClick = mPerformClick; |
132 | performClick.mChild = child; |
133 | performClick.mClickMotionPosition = motionPosition; |
134 | performClick.rememberWindowAttachCount(); |
135 | mResurrectToPosition = motionPosition; |
136 | if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { |
137 | final Handler handler = getHandler(); |
138 | if (handler != null ) { |
139 | handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? |
140 | mPendingCheckForTap : mPendingCheckForLongPress); |
141 | } |
142 | mLayoutMode = LAYOUT_NORMAL; |
143 | if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { |
144 | mTouchMode = TOUCH_MODE_TAP; |
145 | setSelectedPositionInt(mMotionPosition); |
146 | layoutChildren(); |
147 | child.setPressed( true ); |
148 | positionSelector(child); |
149 | setPressed( true ); |
150 | if (mSelector != null ) { |
151 | Drawable d = mSelector.getCurrent(); |
152 | if (d != null && d instanceof TransitionDrawable) { |
153 | ((TransitionDrawable) d).resetTransition(); |
154 | } |
155 | } |
156 | postDelayed( new Runnable() { |
157 | public void run() { |
158 | child.setPressed( false ); |
159 | setPressed( false ); |
160 | if (!mDataChanged) { |
161 | post(performClick); |
162 | } |
163 | mTouchMode = TOUCH_MODE_REST; |
164 | } |
165 | }, ViewConfiguration.getPressedStateDuration()); |
166 | } else { |
167 | mTouchMode = TOUCH_MODE_REST; |
168 | } |
169 | return true ; |
170 | } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { |
171 | post(performClick); |
172 | } |
173 | } |
174 | mTouchMode = TOUCH_MODE_REST; |
175 | break ; |
176 | case TOUCH_MODE_SCROLL: |
177 | final int childCount = getChildCount(); |
178 | if (childCount > 0 ) { |
179 | if (mFirstPosition == 0 && getChildAt( 0 ).getTop() >= mListPadding.top && |
180 | mFirstPosition + childCount < mItemCount && |
181 | getChildAt(childCount - 1 ).getBottom() <= |
182 | getHeight() - mListPadding.bottom) { |
183 | mTouchMode = TOUCH_MODE_REST; |
184 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
185 | setVerticalFadingEdgeEnabled( true ); |
186 | } else { |
187 | final VelocityTracker velocityTracker = mVelocityTracker; |
188 | velocityTracker.computeCurrentVelocity( 1000 , mMaximumVelocity); |
189 | final int initialVelocity = ( int ) velocityTracker.getYVelocity(mActivePointerId); |
190 | |
191 | if (Math.abs(initialVelocity) > mMinimumVelocity) { |
192 | if (mFlingRunnable == null ) { |
193 | mFlingRunnable = new FlingRunnable(); |
194 | } |
195 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); |
196 | |
197 | mFlingRunnable.start(-initialVelocity); |
198 | } else { |
199 | mTouchMode = TOUCH_MODE_REST; |
200 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
201 | setVerticalFadingEdgeEnabled( true ); |
202 | } |
203 | } |
204 | } else { |
205 | mTouchMode = TOUCH_MODE_REST; |
206 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
207 | setVerticalFadingEdgeEnabled( true ); |
208 | } |
209 | break ; |
210 | } |
211 | setPressed( false ); |
212 | // Need to redraw since we probably aren't drawing the selector anymore |
213 | invalidate(); |
214 | final Handler handler = getHandler(); |
215 | if (handler != null ) { |
216 | handler.removeCallbacks(mPendingCheckForLongPress); |
217 | } |
218 | if (mVelocityTracker != null ) { |
219 | mVelocityTracker.recycle(); |
220 | mVelocityTracker = null ; |
221 | } |
222 | |
223 | mActivePointerId = INVALID_POINTER; |
224 | if (PROFILE_SCROLLING) { |
225 | if (mScrollProfilingStarted) { |
226 | Debug.stopMethodTracing(); |
227 | mScrollProfilingStarted = false ; |
228 | } |
229 | } |
230 | break ; |
231 | } |
232 | case MotionEvent.ACTION_CANCEL: { |
233 | mTouchMode = TOUCH_MODE_REST; |
234 | setPressed( false ); |
235 | View motionView = this .getChildAt(mMotionPosition - mFirstPosition); |
236 | if (motionView != null ) { |
237 | motionView.setPressed( false ); |
238 | } |
239 | clearScrollingCache(); |
240 | final Handler handler = getHandler(); |
241 | if (handler != null ) { |
242 | handler.removeCallbacks(mPendingCheckForLongPress); |
243 | } |
244 | if (mVelocityTracker != null ) { |
245 | mVelocityTracker.recycle(); |
246 | mVelocityTracker = null ; |
247 | } |
248 | |
249 | mActivePointerId = INVALID_POINTER; |
250 | break ; |
251 | } |
252 | |
253 | case MotionEvent.ACTION_POINTER_UP: { |
254 | onSecondaryPointerUp(ev); |
255 | final int x = mMotionX; |
256 | final int y = mMotionY; |
257 | final int motionPosition = pointToPosition(x, y); |
258 | if (motionPosition >= 0 ) { |
259 | // Remember where the motion event started |
260 | v = getChildAt(motionPosition - mFirstPosition); |
261 | mMotionViewOriginalTop = v.getTop(); |
262 | mMotionPosition = motionPosition; |
263 | } |
264 | mLastY = y; |
265 | break ; |
266 | } |
267 | } |
268 | return true ; |
269 | } |
270 | ======================================================================== |
271 | private class FlingRunnable implements Runnable { |
272 | |
273 | private final Scroller mScroller; |
274 | |
275 | private int mLastFlingY; |
276 | FlingRunnable() { |
277 | mScroller = new Scroller(getContext()); |
278 | } |
279 | void start( int initialVelocity) { |
280 | int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0 ; |
281 | mLastFlingY = initialY; |
282 | mScroller.fling( 0 , initialY, 0 , initialVelocity, |
283 | 0 , Integer.MAX_VALUE, 0 , Integer.MAX_VALUE); |
284 | mTouchMode = TOUCH_MODE_FLING; |
285 | post( this ); |
286 | if (PROFILE_FLINGING) { |
287 | if (!mFlingProfilingStarted) { |
288 | Debug.startMethodTracing( "AbsListViewFling" ); |
289 | mFlingProfilingStarted = true ; |
290 | } |
291 | } |
292 | } |
293 | void startScroll( int distance, int duration) { |
294 | int initialY = distance < 0 ? Integer.MAX_VALUE : 0 ; |
295 | mLastFlingY = initialY; |
296 | mScroller.startScroll( 0 , initialY, 0 , distance, duration); |
297 | mTouchMode = TOUCH_MODE_FLING; |
298 | post( this ); |
299 | } |
300 | private void endFling() { |
301 | mTouchMode = TOUCH_MODE_REST; |
302 | reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); |
303 | clearScrollingCache(); |
304 | removeCallbacks( this ); |
305 | if (mPositionScroller != null ) { |
306 | removeCallbacks(mPositionScroller); |
307 | } |
308 | } |
309 | public void run() { |
310 | switch (mTouchMode) { |
311 | default : |
312 | return ; |
313 | |
314 | case TOUCH_MODE_FLING: { |
315 | if (mItemCount == 0 || getChildCount() == 0 ) { |
316 | endFling(); |
317 | return ; |
318 | } |
319 | final Scroller scroller = mScroller; |
320 | boolean more = scroller.computeScrollOffset(); |
321 | final int y = scroller.getCurrY(); |
322 | // Flip sign to convert finger direction to list items direction |
323 | // (e.g. finger moving down means list is moving towards the top) |
324 | int delta = mLastFlingY - y; |
325 | // Pretend that each frame of a fling scroll is a touch scroll |
326 | if (delta > 0 ) { |
327 | // List is moving towards the top. Use first view as mMotionPosition |
328 | mMotionPosition = mFirstPosition; |
329 | final View firstView = getChildAt( 0 ); |
330 | mMotionViewOriginalTop = firstView.getTop(); |
331 | // Don't fling more than 1 screen |
332 | delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1 , delta); |
333 | } else { |
334 | // List is moving towards the bottom. Use last view as mMotionPosition |
335 | int offsetToLast = getChildCount() - 1 ; |
336 | mMotionPosition = mFirstPosition + offsetToLast; |
337 | final View lastView = getChildAt(offsetToLast); |
338 | mMotionViewOriginalTop = lastView.getTop(); |
339 | // Don't fling more than 1 screen |
340 | delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1 ), delta); |
341 | } |
342 | final boolean atEnd = trackMotionScroll(delta, delta); |
343 | if (more && !atEnd) { |
344 | invalidate(); |
345 | mLastFlingY = y; |
346 | post( this ); |
347 | } else { |
348 | endFling(); |
349 | AbsListView. this .setVerticalFadingEdgeEnabled( true ); |
350 | if (PROFILE_FLINGING) { |
351 | if (mFlingProfilingStarted) { |
352 | Debug.stopMethodTracing(); |
353 | mFlingProfilingStarted = false ; |
354 | } |
355 | } |
356 | } |
357 | break ; |
358 | } |
359 | } |
360 | } |
361 | } |