RecentsHorizontalScrollView.java revision 99a9655b9333c3bff6e462b12baa56a5fcd4cb20
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.recent; 18 19import android.animation.LayoutTransition; 20import android.content.Context; 21import android.content.res.Configuration; 22import android.database.DataSetObserver; 23import android.graphics.Canvas; 24import android.util.AttributeSet; 25import android.util.DisplayMetrics; 26import android.util.FloatMath; 27import android.util.Log; 28import android.view.MotionEvent; 29import android.view.View; 30import android.view.View.MeasureSpec; 31import android.view.View.OnClickListener; 32import android.view.View.OnLongClickListener; 33import android.view.View.OnTouchListener; 34import android.view.ViewConfiguration; 35import android.view.ViewTreeObserver; 36import android.view.ViewTreeObserver.OnGlobalLayoutListener; 37import android.widget.HorizontalScrollView; 38import android.widget.LinearLayout; 39 40import com.android.systemui.R; 41import com.android.systemui.SwipeHelper; 42import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter; 43 44import java.util.ArrayList; 45 46public class RecentsHorizontalScrollView extends HorizontalScrollView 47 implements SwipeHelper.Callback { 48 private static final String TAG = RecentsPanelView.TAG; 49 private static final boolean DEBUG = RecentsPanelView.DEBUG; 50 private LinearLayout mLinearLayout; 51 private TaskDescriptionAdapter mAdapter; 52 private RecentsCallback mCallback; 53 protected int mLastScrollPosition; 54 private SwipeHelper mSwipeHelper; 55 private RecentsScrollViewPerformanceHelper mPerformanceHelper; 56 private ArrayList<View> mRecycledViews; 57 private int mNumItemsInOneScreenful; 58 59 public RecentsHorizontalScrollView(Context context, AttributeSet attrs) { 60 super(context, attrs, 0); 61 float densityScale = getResources().getDisplayMetrics().density; 62 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); 63 mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop); 64 mPerformanceHelper = RecentsScrollViewPerformanceHelper.create(context, attrs, this, false); 65 mRecycledViews = new ArrayList<View>(); 66 } 67 68 private int scrollPositionOfMostRecent() { 69 return mLinearLayout.getWidth() - getWidth(); 70 } 71 72 private void update() { 73 for (int i = 0; i < mLinearLayout.getChildCount(); i++) { 74 View v = mLinearLayout.getChildAt(i); 75 mRecycledViews.add(v); 76 mAdapter.recycleView(v); 77 } 78 LayoutTransition transitioner = getLayoutTransition(); 79 setLayoutTransition(null); 80 81 mLinearLayout.removeAllViews(); 82 for (int i = 0; i < mAdapter.getCount(); i++) { 83 View old = null; 84 if (mRecycledViews.size() != 0) { 85 old = mRecycledViews.remove(mRecycledViews.size() - 1); 86 old.setVisibility(VISIBLE); 87 } 88 89 final View view = mAdapter.getView(i, old, mLinearLayout); 90 91 if (mPerformanceHelper != null) { 92 mPerformanceHelper.addViewCallback(view); 93 } 94 95 OnTouchListener noOpListener = new OnTouchListener() { 96 @Override 97 public boolean onTouch(View v, MotionEvent event) { 98 return true; 99 } 100 }; 101 102 view.setOnClickListener(new OnClickListener() { 103 public void onClick(View v) { 104 mCallback.dismiss(); 105 } 106 }); 107 // We don't want a click sound when we dimiss recents 108 view.setSoundEffectsEnabled(false); 109 110 OnClickListener launchAppListener = new OnClickListener() { 111 public void onClick(View v) { 112 mCallback.handleOnClick(view); 113 } 114 }; 115 116 RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag(); 117 final View thumbnailView = holder.thumbnailView; 118 OnLongClickListener longClickListener = new OnLongClickListener() { 119 public boolean onLongClick(View v) { 120 final View anchorView = view.findViewById(R.id.app_description); 121 mCallback.handleLongPress(view, anchorView, thumbnailView); 122 return true; 123 } 124 }; 125 thumbnailView.setClickable(true); 126 thumbnailView.setOnClickListener(launchAppListener); 127 thumbnailView.setOnLongClickListener(longClickListener); 128 129 // We don't want to dismiss recents if a user clicks on the app title 130 // (we also don't want to launch the app either, though, because the 131 // app title is a small target and doesn't have great click feedback) 132 final View appTitle = view.findViewById(R.id.app_label); 133 appTitle.setContentDescription(" "); 134 appTitle.setOnTouchListener(noOpListener); 135 mLinearLayout.addView(view); 136 } 137 setLayoutTransition(transitioner); 138 139 // Scroll to end after layout. 140 final ViewTreeObserver observer = getViewTreeObserver(); 141 142 final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() { 143 public void onGlobalLayout() { 144 mLastScrollPosition = scrollPositionOfMostRecent(); 145 scrollTo(mLastScrollPosition, 0); 146 if (observer.isAlive()) { 147 observer.removeOnGlobalLayoutListener(this); 148 } 149 } 150 }; 151 observer.addOnGlobalLayoutListener(updateScroll); 152 } 153 154 @Override 155 public void removeViewInLayout(final View view) { 156 dismissChild(view); 157 } 158 159 public boolean onInterceptTouchEvent(MotionEvent ev) { 160 if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); 161 return mSwipeHelper.onInterceptTouchEvent(ev) || 162 super.onInterceptTouchEvent(ev); 163 } 164 165 @Override 166 public boolean onTouchEvent(MotionEvent ev) { 167 return mSwipeHelper.onTouchEvent(ev) || 168 super.onTouchEvent(ev); 169 } 170 171 public boolean canChildBeDismissed(View v) { 172 return true; 173 } 174 175 public void dismissChild(View v) { 176 mSwipeHelper.dismissChild(v, 0); 177 } 178 179 public void onChildDismissed(View v) { 180 mRecycledViews.add(v); 181 mLinearLayout.removeView(v); 182 mCallback.handleSwipe(v); 183 v.setActivated(false); 184 } 185 186 public void onBeginDrag(View v) { 187 // We do this so the underlying ScrollView knows that it won't get 188 // the chance to intercept events anymore 189 requestDisallowInterceptTouchEvent(true); 190 v.setActivated(true); 191 } 192 193 public void onDragCancelled(View v) { 194 v.setActivated(false); 195 } 196 197 public View getChildAtPosition(MotionEvent ev) { 198 final float x = ev.getX() + getScrollX(); 199 final float y = ev.getY() + getScrollY(); 200 for (int i = 0; i < mLinearLayout.getChildCount(); i++) { 201 View item = mLinearLayout.getChildAt(i); 202 if (x >= item.getLeft() && x < item.getRight() 203 && y >= item.getTop() && y < item.getBottom()) { 204 return item; 205 } 206 } 207 return null; 208 } 209 210 public View getChildContentView(View v) { 211 return v.findViewById(R.id.recent_item); 212 } 213 214 @Override 215 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 216 super.onLayout(changed, left, top, right, bottom); 217 if (mPerformanceHelper != null) { 218 mPerformanceHelper.onLayoutCallback(); 219 } 220 } 221 222 @Override 223 public void draw(Canvas canvas) { 224 super.draw(canvas); 225 226 if (mPerformanceHelper != null) { 227 int paddingLeft = mPaddingLeft; 228 final boolean offsetRequired = isPaddingOffsetRequired(); 229 if (offsetRequired) { 230 paddingLeft += getLeftPaddingOffset(); 231 } 232 233 int left = mScrollX + paddingLeft; 234 int right = left + mRight - mLeft - mPaddingRight - paddingLeft; 235 int top = mScrollY + getFadeTop(offsetRequired); 236 int bottom = top + getFadeHeight(offsetRequired); 237 238 if (offsetRequired) { 239 right += getRightPaddingOffset(); 240 bottom += getBottomPaddingOffset(); 241 } 242 mPerformanceHelper.drawCallback(canvas, 243 left, right, top, bottom, mScrollX, mScrollY, 244 0, 0, 245 getLeftFadingEdgeStrength(), getRightFadingEdgeStrength()); 246 } 247 } 248 249 @Override 250 public int getVerticalFadingEdgeLength() { 251 if (mPerformanceHelper != null) { 252 return mPerformanceHelper.getVerticalFadingEdgeLengthCallback(); 253 } else { 254 return super.getVerticalFadingEdgeLength(); 255 } 256 } 257 258 @Override 259 public int getHorizontalFadingEdgeLength() { 260 if (mPerformanceHelper != null) { 261 return mPerformanceHelper.getHorizontalFadingEdgeLengthCallback(); 262 } else { 263 return super.getHorizontalFadingEdgeLength(); 264 } 265 } 266 267 @Override 268 protected void onFinishInflate() { 269 super.onFinishInflate(); 270 setScrollbarFadingEnabled(true); 271 mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout); 272 final int leftPadding = mContext.getResources() 273 .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin); 274 setOverScrollEffectPadding(leftPadding, 0); 275 } 276 277 @Override 278 public void onAttachedToWindow() { 279 if (mPerformanceHelper != null) { 280 mPerformanceHelper.onAttachedToWindowCallback( 281 mCallback, mLinearLayout, isHardwareAccelerated()); 282 } 283 } 284 285 @Override 286 protected void onConfigurationChanged(Configuration newConfig) { 287 super.onConfigurationChanged(newConfig); 288 float densityScale = getResources().getDisplayMetrics().density; 289 mSwipeHelper.setDensityScale(densityScale); 290 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); 291 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 292 } 293 294 private void setOverScrollEffectPadding(int leftPadding, int i) { 295 // TODO Add to (Vertical)ScrollView 296 } 297 298 @Override 299 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 300 super.onSizeChanged(w, h, oldw, oldh); 301 302 // Skip this work if a transition is running; it sets the scroll values independently 303 // and should not have those animated values clobbered by this logic 304 LayoutTransition transition = mLinearLayout.getLayoutTransition(); 305 if (transition != null && transition.isRunning()) { 306 return; 307 } 308 // Keep track of the last visible item in the list so we can restore it 309 // to the bottom when the orientation changes. 310 mLastScrollPosition = scrollPositionOfMostRecent(); 311 312 // This has to happen post-layout, so run it "in the future" 313 post(new Runnable() { 314 public void run() { 315 // Make sure we're still not clobbering the transition-set values, since this 316 // runnable launches asynchronously 317 LayoutTransition transition = mLinearLayout.getLayoutTransition(); 318 if (transition == null || !transition.isRunning()) { 319 scrollTo(mLastScrollPosition, 0); 320 } 321 } 322 }); 323 } 324 325 public void onRecentsVisibilityChanged() { 326 if (mPerformanceHelper != null) { 327 mPerformanceHelper.updateShowBackground(); 328 } 329 } 330 331 @Override 332 protected void onVisibilityChanged(View changedView, int visibility) { 333 super.onVisibilityChanged(changedView, visibility); 334 // scroll to bottom after reloading 335 if (visibility == View.VISIBLE && changedView == this) { 336 post(new Runnable() { 337 public void run() { 338 update(); 339 } 340 }); 341 } 342 } 343 344 public void setAdapter(TaskDescriptionAdapter adapter) { 345 mAdapter = adapter; 346 mAdapter.registerDataSetObserver(new DataSetObserver() { 347 public void onChanged() { 348 update(); 349 } 350 351 public void onInvalidated() { 352 update(); 353 } 354 }); 355 DisplayMetrics dm = getResources().getDisplayMetrics(); 356 int childWidthMeasureSpec = 357 MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST); 358 int childheightMeasureSpec = 359 MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST); 360 View child = mAdapter.createView(mLinearLayout); 361 child.measure(childWidthMeasureSpec, childheightMeasureSpec); 362 mNumItemsInOneScreenful = 363 (int) FloatMath.ceil(dm.widthPixels / (float) child.getMeasuredWidth()); 364 mRecycledViews.add(child); 365 366 for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) { 367 mRecycledViews.add(mAdapter.createView(mLinearLayout)); 368 } 369 } 370 371 public int numItemsInOneScreenful() { 372 return mNumItemsInOneScreenful; 373 } 374 375 @Override 376 public void setLayoutTransition(LayoutTransition transition) { 377 if (mPerformanceHelper != null) { 378 mPerformanceHelper.setLayoutTransitionCallback(transition); 379 } 380 // The layout transition applies to our embedded LinearLayout 381 mLinearLayout.setLayoutTransition(transition); 382 } 383 384 public void setCallback(RecentsCallback callback) { 385 mCallback = callback; 386 } 387} 388