|
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 package org.mozilla.gecko; |
|
7 |
|
8 import java.util.ArrayList; |
|
9 import java.util.List; |
|
10 |
|
11 import org.mozilla.gecko.animation.PropertyAnimator; |
|
12 import org.mozilla.gecko.animation.PropertyAnimator.Property; |
|
13 import org.mozilla.gecko.animation.ViewHelper; |
|
14 import org.mozilla.gecko.widget.TwoWayView; |
|
15 import org.mozilla.gecko.widget.TabThumbnailWrapper; |
|
16 |
|
17 import android.content.Context; |
|
18 import android.content.res.TypedArray; |
|
19 import android.graphics.Rect; |
|
20 import android.graphics.drawable.Drawable; |
|
21 import android.util.AttributeSet; |
|
22 import android.view.LayoutInflater; |
|
23 import android.view.MotionEvent; |
|
24 import android.view.VelocityTracker; |
|
25 import android.view.View; |
|
26 import android.view.ViewConfiguration; |
|
27 import android.view.ViewGroup; |
|
28 import android.widget.BaseAdapter; |
|
29 import android.widget.Button; |
|
30 import android.widget.ImageButton; |
|
31 import android.widget.ImageView; |
|
32 import android.widget.TextView; |
|
33 |
|
34 public class TabsTray extends TwoWayView |
|
35 implements TabsPanel.PanelView { |
|
36 private static final String LOGTAG = "GeckoTabsTray"; |
|
37 |
|
38 private Context mContext; |
|
39 private TabsPanel mTabsPanel; |
|
40 |
|
41 private TabsAdapter mTabsAdapter; |
|
42 |
|
43 private List<View> mPendingClosedTabs; |
|
44 private int mCloseAnimationCount; |
|
45 |
|
46 private TabSwipeGestureListener mSwipeListener; |
|
47 |
|
48 // Time to animate non-flinged tabs of screen, in milliseconds |
|
49 private static final int ANIMATION_DURATION = 250; |
|
50 |
|
51 private static final String ABOUT_HOME = "about:home"; |
|
52 private int mOriginalSize = 0; |
|
53 |
|
54 public TabsTray(Context context, AttributeSet attrs) { |
|
55 super(context, attrs); |
|
56 mContext = context; |
|
57 |
|
58 mCloseAnimationCount = 0; |
|
59 mPendingClosedTabs = new ArrayList<View>(); |
|
60 |
|
61 setItemsCanFocus(true); |
|
62 |
|
63 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsTray); |
|
64 boolean isPrivate = (a.getInt(R.styleable.TabsTray_tabs, 0x0) == 1); |
|
65 a.recycle(); |
|
66 |
|
67 mTabsAdapter = new TabsAdapter(mContext, isPrivate); |
|
68 setAdapter(mTabsAdapter); |
|
69 |
|
70 mSwipeListener = new TabSwipeGestureListener(); |
|
71 setOnTouchListener(mSwipeListener); |
|
72 setOnScrollListener(mSwipeListener.makeScrollListener()); |
|
73 |
|
74 setRecyclerListener(new RecyclerListener() { |
|
75 @Override |
|
76 public void onMovedToScrapHeap(View view) { |
|
77 TabRow row = (TabRow) view.getTag(); |
|
78 row.thumbnail.setImageDrawable(null); |
|
79 row.close.setVisibility(View.VISIBLE); |
|
80 } |
|
81 }); |
|
82 } |
|
83 |
|
84 @Override |
|
85 public ViewGroup getLayout() { |
|
86 return this; |
|
87 } |
|
88 |
|
89 @Override |
|
90 public void setTabsPanel(TabsPanel panel) { |
|
91 mTabsPanel = panel; |
|
92 } |
|
93 |
|
94 @Override |
|
95 public void show() { |
|
96 setVisibility(View.VISIBLE); |
|
97 Tabs.getInstance().refreshThumbnails(); |
|
98 Tabs.registerOnTabsChangedListener(mTabsAdapter); |
|
99 mTabsAdapter.refreshTabsData(); |
|
100 } |
|
101 |
|
102 @Override |
|
103 public void hide() { |
|
104 setVisibility(View.GONE); |
|
105 Tabs.unregisterOnTabsChangedListener(mTabsAdapter); |
|
106 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Screenshot:Cancel","")); |
|
107 mTabsAdapter.clear(); |
|
108 } |
|
109 |
|
110 @Override |
|
111 public boolean shouldExpand() { |
|
112 return isVertical(); |
|
113 } |
|
114 |
|
115 private void autoHidePanel() { |
|
116 mTabsPanel.autoHidePanel(); |
|
117 } |
|
118 |
|
119 // ViewHolder for a row in the list |
|
120 private class TabRow { |
|
121 int id; |
|
122 TextView title; |
|
123 ImageView thumbnail; |
|
124 ImageButton close; |
|
125 ViewGroup info; |
|
126 TabThumbnailWrapper thumbnailWrapper; |
|
127 |
|
128 public TabRow(View view) { |
|
129 info = (ViewGroup) view; |
|
130 title = (TextView) view.findViewById(R.id.title); |
|
131 thumbnail = (ImageView) view.findViewById(R.id.thumbnail); |
|
132 close = (ImageButton) view.findViewById(R.id.close); |
|
133 thumbnailWrapper = (TabThumbnailWrapper) view.findViewById(R.id.wrapper); |
|
134 } |
|
135 } |
|
136 |
|
137 // Adapter to bind tabs into a list |
|
138 private class TabsAdapter extends BaseAdapter implements Tabs.OnTabsChangedListener { |
|
139 private Context mContext; |
|
140 private boolean mIsPrivate; |
|
141 private ArrayList<Tab> mTabs; |
|
142 private LayoutInflater mInflater; |
|
143 private Button.OnClickListener mOnCloseClickListener; |
|
144 |
|
145 public TabsAdapter(Context context, boolean isPrivate) { |
|
146 mContext = context; |
|
147 mInflater = LayoutInflater.from(mContext); |
|
148 mIsPrivate = isPrivate; |
|
149 |
|
150 mOnCloseClickListener = new Button.OnClickListener() { |
|
151 @Override |
|
152 public void onClick(View v) { |
|
153 TabRow tab = (TabRow) v.getTag(); |
|
154 final int pos = (isVertical() ? tab.info.getWidth() : tab.info.getHeight()); |
|
155 animateClose(tab.info, pos); |
|
156 } |
|
157 }; |
|
158 } |
|
159 |
|
160 @Override |
|
161 public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) { |
|
162 switch (msg) { |
|
163 case ADDED: |
|
164 // Refresh the list to make sure the new tab is added in the right position. |
|
165 refreshTabsData(); |
|
166 break; |
|
167 |
|
168 case CLOSED: |
|
169 removeTab(tab); |
|
170 break; |
|
171 |
|
172 case SELECTED: |
|
173 // Update the selected position, then fall through... |
|
174 updateSelectedPosition(); |
|
175 case UNSELECTED: |
|
176 // We just need to update the style for the unselected tab... |
|
177 case THUMBNAIL: |
|
178 case TITLE: |
|
179 case RECORDING_CHANGE: |
|
180 View view = TabsTray.this.getChildAt(getPositionForTab(tab) - TabsTray.this.getFirstVisiblePosition()); |
|
181 if (view == null) |
|
182 return; |
|
183 |
|
184 TabRow row = (TabRow) view.getTag(); |
|
185 assignValues(row, tab); |
|
186 break; |
|
187 } |
|
188 } |
|
189 |
|
190 private void refreshTabsData() { |
|
191 // Store a different copy of the tabs, so that we don't have to worry about |
|
192 // accidentally updating it on the wrong thread. |
|
193 mTabs = new ArrayList<Tab>(); |
|
194 |
|
195 Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder(); |
|
196 for (Tab tab : tabs) { |
|
197 if (tab.isPrivate() == mIsPrivate) |
|
198 mTabs.add(tab); |
|
199 } |
|
200 |
|
201 notifyDataSetChanged(); // Be sure to call this whenever mTabs changes. |
|
202 updateSelectedPosition(); |
|
203 } |
|
204 |
|
205 // Updates the selected position in the list so that it will be scrolled to the right place. |
|
206 private void updateSelectedPosition() { |
|
207 int selected = getPositionForTab(Tabs.getInstance().getSelectedTab()); |
|
208 updateSelectedStyle(selected); |
|
209 |
|
210 if (selected != -1) { |
|
211 TabsTray.this.setSelection(selected); |
|
212 } |
|
213 } |
|
214 |
|
215 /** |
|
216 * Updates the selected/unselected style for the tabs. |
|
217 * |
|
218 * @param selected position of the selected tab |
|
219 */ |
|
220 private void updateSelectedStyle(int selected) { |
|
221 for (int i = 0; i < getCount(); i++) { |
|
222 TabsTray.this.setItemChecked(i, (i == selected)); |
|
223 } |
|
224 } |
|
225 |
|
226 public void clear() { |
|
227 mTabs = null; |
|
228 notifyDataSetChanged(); // Be sure to call this whenever mTabs changes. |
|
229 } |
|
230 |
|
231 @Override |
|
232 public int getCount() { |
|
233 return (mTabs == null ? 0 : mTabs.size()); |
|
234 } |
|
235 |
|
236 @Override |
|
237 public Tab getItem(int position) { |
|
238 return mTabs.get(position); |
|
239 } |
|
240 |
|
241 @Override |
|
242 public long getItemId(int position) { |
|
243 return position; |
|
244 } |
|
245 |
|
246 private int getPositionForTab(Tab tab) { |
|
247 if (mTabs == null || tab == null) |
|
248 return -1; |
|
249 |
|
250 return mTabs.indexOf(tab); |
|
251 } |
|
252 |
|
253 private void removeTab(Tab tab) { |
|
254 if (tab.isPrivate() == mIsPrivate && mTabs != null) { |
|
255 mTabs.remove(tab); |
|
256 notifyDataSetChanged(); // Be sure to call this whenever mTabs changes. |
|
257 |
|
258 int selected = getPositionForTab(Tabs.getInstance().getSelectedTab()); |
|
259 updateSelectedStyle(selected); |
|
260 } |
|
261 } |
|
262 |
|
263 private void assignValues(TabRow row, Tab tab) { |
|
264 if (row == null || tab == null) |
|
265 return; |
|
266 |
|
267 row.id = tab.getId(); |
|
268 |
|
269 Drawable thumbnailImage = tab.getThumbnail(); |
|
270 if (thumbnailImage != null) { |
|
271 row.thumbnail.setImageDrawable(thumbnailImage); |
|
272 } else if (AboutPages.isAboutHome(tab.getURL())) { |
|
273 row.thumbnail.setImageResource(R.drawable.abouthome_thumbnail); |
|
274 } else { |
|
275 row.thumbnail.setImageResource(R.drawable.tab_thumbnail_default); |
|
276 } |
|
277 if (row.thumbnailWrapper != null) { |
|
278 row.thumbnailWrapper.setRecording(tab.isRecording()); |
|
279 } |
|
280 row.title.setText(tab.getDisplayTitle()); |
|
281 row.close.setTag(row); |
|
282 } |
|
283 |
|
284 private void resetTransforms(View view) { |
|
285 ViewHelper.setAlpha(view, 1); |
|
286 if (mOriginalSize == 0) |
|
287 return; |
|
288 |
|
289 if (isVertical()) { |
|
290 ViewHelper.setHeight(view, mOriginalSize); |
|
291 ViewHelper.setTranslationX(view, 0); |
|
292 } else { |
|
293 ViewHelper.setWidth(view, mOriginalSize); |
|
294 ViewHelper.setTranslationY(view, 0); |
|
295 } |
|
296 } |
|
297 |
|
298 @Override |
|
299 public View getView(int position, View convertView, ViewGroup parent) { |
|
300 TabRow row; |
|
301 |
|
302 if (convertView == null) { |
|
303 convertView = mInflater.inflate(R.layout.tabs_row, null); |
|
304 row = new TabRow(convertView); |
|
305 row.close.setOnClickListener(mOnCloseClickListener); |
|
306 convertView.setTag(row); |
|
307 } else { |
|
308 row = (TabRow) convertView.getTag(); |
|
309 // If we're recycling this view, there's a chance it was transformed during |
|
310 // the close animation. Remove any of those properties. |
|
311 resetTransforms(convertView); |
|
312 } |
|
313 |
|
314 Tab tab = mTabs.get(position); |
|
315 assignValues(row, tab); |
|
316 |
|
317 return convertView; |
|
318 } |
|
319 } |
|
320 |
|
321 private boolean isVertical() { |
|
322 return (getOrientation().compareTo(TwoWayView.Orientation.VERTICAL) == 0); |
|
323 } |
|
324 |
|
325 private void animateClose(final View view, int pos) { |
|
326 PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION); |
|
327 animator.attach(view, Property.ALPHA, 0); |
|
328 |
|
329 if (isVertical()) |
|
330 animator.attach(view, Property.TRANSLATION_X, pos); |
|
331 else |
|
332 animator.attach(view, Property.TRANSLATION_Y, pos); |
|
333 |
|
334 mCloseAnimationCount++; |
|
335 mPendingClosedTabs.add(view); |
|
336 |
|
337 animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { |
|
338 @Override |
|
339 public void onPropertyAnimationStart() { } |
|
340 @Override |
|
341 public void onPropertyAnimationEnd() { |
|
342 mCloseAnimationCount--; |
|
343 if (mCloseAnimationCount > 0) |
|
344 return; |
|
345 |
|
346 for (View pendingView : mPendingClosedTabs) { |
|
347 animateFinishClose(pendingView); |
|
348 } |
|
349 |
|
350 mPendingClosedTabs.clear(); |
|
351 } |
|
352 }); |
|
353 |
|
354 if (mTabsAdapter.getCount() == 1) |
|
355 autoHidePanel(); |
|
356 |
|
357 animator.start(); |
|
358 } |
|
359 |
|
360 private void animateFinishClose(final View view) { |
|
361 PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION); |
|
362 |
|
363 final boolean isVertical = isVertical(); |
|
364 if (isVertical) |
|
365 animator.attach(view, Property.HEIGHT, 1); |
|
366 else |
|
367 animator.attach(view, Property.WIDTH, 1); |
|
368 |
|
369 TabRow tab = (TabRow)view.getTag(); |
|
370 final int tabId = tab.id; |
|
371 // Caching this assumes that all rows are the same height |
|
372 if (mOriginalSize == 0) |
|
373 mOriginalSize = (isVertical ? view.getHeight() : view.getWidth()); |
|
374 |
|
375 animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { |
|
376 @Override |
|
377 public void onPropertyAnimationStart() { } |
|
378 @Override |
|
379 public void onPropertyAnimationEnd() { |
|
380 Tabs tabs = Tabs.getInstance(); |
|
381 Tab tab = tabs.getTab(tabId); |
|
382 tabs.closeTab(tab); |
|
383 } |
|
384 }); |
|
385 |
|
386 animator.start(); |
|
387 } |
|
388 |
|
389 private void animateCancel(final View view) { |
|
390 PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION); |
|
391 animator.attach(view, Property.ALPHA, 1); |
|
392 |
|
393 if (isVertical()) |
|
394 animator.attach(view, Property.TRANSLATION_X, 0); |
|
395 else |
|
396 animator.attach(view, Property.TRANSLATION_Y, 0); |
|
397 |
|
398 |
|
399 animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() { |
|
400 @Override |
|
401 public void onPropertyAnimationStart() { } |
|
402 @Override |
|
403 public void onPropertyAnimationEnd() { |
|
404 TabRow tab = (TabRow) view.getTag(); |
|
405 tab.close.setVisibility(View.VISIBLE); |
|
406 } |
|
407 }); |
|
408 |
|
409 animator.start(); |
|
410 } |
|
411 |
|
412 private class TabSwipeGestureListener implements View.OnTouchListener { |
|
413 // same value the stock browser uses for after drag animation velocity in pixels/sec |
|
414 // http://androidxref.com/4.0.4/xref/packages/apps/Browser/src/com/android/browser/NavTabScroller.java#61 |
|
415 private static final float MIN_VELOCITY = 750; |
|
416 |
|
417 private int mSwipeThreshold; |
|
418 private int mMinFlingVelocity; |
|
419 |
|
420 private int mMaxFlingVelocity; |
|
421 private VelocityTracker mVelocityTracker; |
|
422 |
|
423 private int mListWidth = 1; |
|
424 private int mListHeight = 1; |
|
425 |
|
426 private View mSwipeView; |
|
427 private int mSwipeViewPosition; |
|
428 private Runnable mPendingCheckForTap; |
|
429 |
|
430 private float mSwipeStartX; |
|
431 private float mSwipeStartY; |
|
432 private boolean mSwiping; |
|
433 private boolean mEnabled; |
|
434 |
|
435 public TabSwipeGestureListener() { |
|
436 mSwipeView = null; |
|
437 mSwipeViewPosition = TwoWayView.INVALID_POSITION; |
|
438 mSwiping = false; |
|
439 mEnabled = true; |
|
440 |
|
441 ViewConfiguration vc = ViewConfiguration.get(TabsTray.this.getContext()); |
|
442 mSwipeThreshold = vc.getScaledTouchSlop(); |
|
443 mMinFlingVelocity = (int) (getContext().getResources().getDisplayMetrics().density * MIN_VELOCITY); |
|
444 mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); |
|
445 } |
|
446 |
|
447 public void setEnabled(boolean enabled) { |
|
448 mEnabled = enabled; |
|
449 } |
|
450 |
|
451 public TwoWayView.OnScrollListener makeScrollListener() { |
|
452 return new TwoWayView.OnScrollListener() { |
|
453 @Override |
|
454 public void onScrollStateChanged(TwoWayView twoWayView, int scrollState) { |
|
455 setEnabled(scrollState != TwoWayView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); |
|
456 } |
|
457 |
|
458 @Override |
|
459 public void onScroll(TwoWayView twoWayView, int i, int i1, int i2) { |
|
460 } |
|
461 }; |
|
462 } |
|
463 |
|
464 @Override |
|
465 public boolean onTouch(View view, MotionEvent e) { |
|
466 if (!mEnabled) |
|
467 return false; |
|
468 |
|
469 if (mListWidth < 2 || mListHeight < 2) { |
|
470 mListWidth = TabsTray.this.getWidth(); |
|
471 mListHeight = TabsTray.this.getHeight(); |
|
472 } |
|
473 |
|
474 switch (e.getActionMasked()) { |
|
475 case MotionEvent.ACTION_DOWN: { |
|
476 // Check if we should set pressed state on the |
|
477 // touched view after a standard delay. |
|
478 triggerCheckForTap(); |
|
479 |
|
480 final float x = e.getRawX(); |
|
481 final float y = e.getRawY(); |
|
482 |
|
483 // Find out which view is being touched |
|
484 mSwipeView = findViewAt(x, y); |
|
485 |
|
486 if (mSwipeView != null) { |
|
487 mSwipeStartX = e.getRawX(); |
|
488 mSwipeStartY = e.getRawY(); |
|
489 mSwipeViewPosition = TabsTray.this.getPositionForView(mSwipeView); |
|
490 |
|
491 mVelocityTracker = VelocityTracker.obtain(); |
|
492 mVelocityTracker.addMovement(e); |
|
493 } |
|
494 |
|
495 view.onTouchEvent(e); |
|
496 return true; |
|
497 } |
|
498 |
|
499 case MotionEvent.ACTION_UP: { |
|
500 if (mSwipeView == null) |
|
501 break; |
|
502 |
|
503 cancelCheckForTap(); |
|
504 mSwipeView.setPressed(false); |
|
505 |
|
506 if (!mSwiping) { |
|
507 TabRow tab = (TabRow) mSwipeView.getTag(); |
|
508 Tabs.getInstance().selectTab(tab.id); |
|
509 autoHidePanel(); |
|
510 break; |
|
511 } |
|
512 |
|
513 mVelocityTracker.addMovement(e); |
|
514 mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); |
|
515 |
|
516 float velocityX = Math.abs(mVelocityTracker.getXVelocity()); |
|
517 float velocityY = Math.abs(mVelocityTracker.getYVelocity()); |
|
518 |
|
519 boolean dismiss = false; |
|
520 boolean dismissDirection = false; |
|
521 int dismissTranslation = 0; |
|
522 |
|
523 if (isVertical()) { |
|
524 float deltaX = ViewHelper.getTranslationX(mSwipeView); |
|
525 |
|
526 if (Math.abs(deltaX) > mListWidth / 2) { |
|
527 dismiss = true; |
|
528 dismissDirection = (deltaX > 0); |
|
529 } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity |
|
530 && velocityY < velocityX) { |
|
531 dismiss = mSwiping && (deltaX * mVelocityTracker.getXVelocity() > 0); |
|
532 dismissDirection = (mVelocityTracker.getXVelocity() > 0); |
|
533 } |
|
534 |
|
535 dismissTranslation = (dismissDirection ? mListWidth : -mListWidth); |
|
536 } else { |
|
537 float deltaY = ViewHelper.getTranslationY(mSwipeView); |
|
538 |
|
539 if (Math.abs(deltaY) > mListHeight / 2) { |
|
540 dismiss = true; |
|
541 dismissDirection = (deltaY > 0); |
|
542 } else if (mMinFlingVelocity <= velocityY && velocityY <= mMaxFlingVelocity |
|
543 && velocityX < velocityY) { |
|
544 dismiss = mSwiping && (deltaY * mVelocityTracker.getYVelocity() > 0); |
|
545 dismissDirection = (mVelocityTracker.getYVelocity() > 0); |
|
546 } |
|
547 |
|
548 dismissTranslation = (dismissDirection ? mListHeight : -mListHeight); |
|
549 } |
|
550 |
|
551 if (dismiss) |
|
552 animateClose(mSwipeView, dismissTranslation); |
|
553 else |
|
554 animateCancel(mSwipeView); |
|
555 |
|
556 mVelocityTracker = null; |
|
557 mSwipeView = null; |
|
558 mSwipeViewPosition = TwoWayView.INVALID_POSITION; |
|
559 |
|
560 mSwipeStartX = 0; |
|
561 mSwipeStartY = 0; |
|
562 mSwiping = false; |
|
563 |
|
564 break; |
|
565 } |
|
566 |
|
567 case MotionEvent.ACTION_MOVE: { |
|
568 if (mSwipeView == null) |
|
569 break; |
|
570 |
|
571 mVelocityTracker.addMovement(e); |
|
572 |
|
573 final boolean isVertical = isVertical(); |
|
574 |
|
575 float deltaX = e.getRawX() - mSwipeStartX; |
|
576 float deltaY = e.getRawY() - mSwipeStartY; |
|
577 float delta = (isVertical ? deltaX : deltaY); |
|
578 |
|
579 boolean isScrollingX = Math.abs(deltaX) > mSwipeThreshold; |
|
580 boolean isScrollingY = Math.abs(deltaY) > mSwipeThreshold; |
|
581 boolean isSwipingToClose = (isVertical ? isScrollingX : isScrollingY); |
|
582 |
|
583 // If we're actually swiping, make sure we don't |
|
584 // set pressed state on the swiped view. |
|
585 if (isScrollingX || isScrollingY) |
|
586 cancelCheckForTap(); |
|
587 |
|
588 if (isSwipingToClose) { |
|
589 mSwiping = true; |
|
590 TabsTray.this.requestDisallowInterceptTouchEvent(true); |
|
591 |
|
592 TabRow tab = (TabRow) mSwipeView.getTag(); |
|
593 tab.close.setVisibility(View.INVISIBLE); |
|
594 |
|
595 // Stops listview from highlighting the touched item |
|
596 // in the list when swiping. |
|
597 MotionEvent cancelEvent = MotionEvent.obtain(e); |
|
598 cancelEvent.setAction(MotionEvent.ACTION_CANCEL | |
|
599 (e.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); |
|
600 TabsTray.this.onTouchEvent(cancelEvent); |
|
601 } |
|
602 |
|
603 if (mSwiping) { |
|
604 if (isVertical) |
|
605 ViewHelper.setTranslationX(mSwipeView, delta); |
|
606 else |
|
607 ViewHelper.setTranslationY(mSwipeView, delta); |
|
608 |
|
609 ViewHelper.setAlpha(mSwipeView, Math.max(0.1f, Math.min(1f, |
|
610 1f - 2f * Math.abs(delta) / (isVertical ? mListWidth : mListHeight)))); |
|
611 |
|
612 return true; |
|
613 } |
|
614 |
|
615 break; |
|
616 } |
|
617 } |
|
618 |
|
619 return false; |
|
620 } |
|
621 |
|
622 private View findViewAt(float rawX, float rawY) { |
|
623 Rect rect = new Rect(); |
|
624 |
|
625 int[] listViewCoords = new int[2]; |
|
626 TabsTray.this.getLocationOnScreen(listViewCoords); |
|
627 |
|
628 int x = (int) rawX - listViewCoords[0]; |
|
629 int y = (int) rawY - listViewCoords[1]; |
|
630 |
|
631 for (int i = 0; i < TabsTray.this.getChildCount(); i++) { |
|
632 View child = TabsTray.this.getChildAt(i); |
|
633 child.getHitRect(rect); |
|
634 |
|
635 if (rect.contains(x, y)) |
|
636 return child; |
|
637 } |
|
638 |
|
639 return null; |
|
640 } |
|
641 |
|
642 private void triggerCheckForTap() { |
|
643 if (mPendingCheckForTap == null) |
|
644 mPendingCheckForTap = new CheckForTap(); |
|
645 |
|
646 TabsTray.this.postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); |
|
647 } |
|
648 |
|
649 private void cancelCheckForTap() { |
|
650 if (mPendingCheckForTap == null) |
|
651 return; |
|
652 |
|
653 TabsTray.this.removeCallbacks(mPendingCheckForTap); |
|
654 } |
|
655 |
|
656 private class CheckForTap implements Runnable { |
|
657 @Override |
|
658 public void run() { |
|
659 if (!mSwiping && mSwipeView != null && mEnabled) |
|
660 mSwipeView.setPressed(true); |
|
661 } |
|
662 } |
|
663 } |
|
664 } |