RecentsHorizontalScrollView.java revision ab48b681401628a191a4a90d4906fa88edde95ba
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.Log; 26import android.view.MotionEvent; 27import android.view.View; 28import android.view.ViewConfiguration; 29import android.view.View.OnTouchListener; 30import android.widget.HorizontalScrollView; 31import android.widget.LinearLayout; 32 33import com.android.systemui.R; 34import com.android.systemui.SwipeHelper; 35import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter; 36 37public class RecentsHorizontalScrollView extends HorizontalScrollView 38 implements SwipeHelper.Callback { 39 private static final String TAG = RecentsPanelView.TAG; 40 private static final boolean DEBUG = RecentsPanelView.DEBUG; 41 private LinearLayout mLinearLayout; 42 private TaskDescriptionAdapter mAdapter; 43 private RecentsCallback mCallback; 44 protected int mLastScrollPosition; 45 private SwipeHelper mSwipeHelper; 46 private RecentsScrollViewPerformanceHelper mPerformanceHelper; 47 48 public RecentsHorizontalScrollView(Context context, AttributeSet attrs) { 49 super(context, attrs, 0); 50 float densityScale = getResources().getDisplayMetrics().density; 51 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); 52 mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop); 53 mPerformanceHelper = RecentsScrollViewPerformanceHelper.create(context, attrs, this, false); 54 } 55 56 private int scrollPositionOfMostRecent() { 57 return mLinearLayout.getWidth() - getWidth(); 58 } 59 60 private void update() { 61 mLinearLayout.removeAllViews(); 62 for (int i = 0; i < mAdapter.getCount(); i++) { 63 final View view = mAdapter.getView(i, null, mLinearLayout); 64 65 if (mPerformanceHelper != null) { 66 mPerformanceHelper.addViewCallback(view); 67 } 68 69 OnTouchListener noOpListener = new OnTouchListener() { 70 @Override 71 public boolean onTouch(View v, MotionEvent event) { 72 return true; 73 } 74 }; 75 76 view.setOnClickListener(new OnClickListener() { 77 public void onClick(View v) { 78 mCallback.dismiss(); 79 } 80 }); 81 // We don't want a click sound when we dimiss recents 82 view.setSoundEffectsEnabled(false); 83 84 OnClickListener launchAppListener = new OnClickListener() { 85 public void onClick(View v) { 86 mCallback.handleOnClick(view); 87 } 88 }; 89 90 final View thumbnailView = view.findViewById(R.id.app_thumbnail); 91 OnLongClickListener longClickListener = new OnLongClickListener() { 92 public boolean onLongClick(View v) { 93 final View anchorView = view.findViewById(R.id.app_description); 94 mCallback.handleLongPress(view, anchorView, thumbnailView); 95 return true; 96 } 97 }; 98 thumbnailView.setClickable(true); 99 thumbnailView.setOnClickListener(launchAppListener); 100 thumbnailView.setOnLongClickListener(longClickListener); 101 102 // We don't want to dismiss recents if a user clicks on the app title 103 // (we also don't want to launch the app either, though, because the 104 // app title is a small target and doesn't have great click feedback) 105 final View appTitle = view.findViewById(R.id.app_label); 106 appTitle.setContentDescription(" "); 107 appTitle.setOnTouchListener(noOpListener); 108 mLinearLayout.addView(view); 109 } 110 // Scroll to end after layout. 111 post(new Runnable() { 112 public void run() { 113 mLastScrollPosition = scrollPositionOfMostRecent(); 114 scrollTo(mLastScrollPosition, 0); 115 } 116 }); 117 } 118 119 @Override 120 public void removeViewInLayout(final View view) { 121 dismissChild(view); 122 } 123 124 public boolean onInterceptTouchEvent(MotionEvent ev) { 125 if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); 126 return mSwipeHelper.onInterceptTouchEvent(ev) || 127 super.onInterceptTouchEvent(ev); 128 } 129 130 @Override 131 public boolean onTouchEvent(MotionEvent ev) { 132 return mSwipeHelper.onTouchEvent(ev) || 133 super.onTouchEvent(ev); 134 } 135 136 public boolean canChildBeDismissed(View v) { 137 return true; 138 } 139 140 public void dismissChild(View v) { 141 mSwipeHelper.dismissChild(v, 0); 142 } 143 144 public void onChildDismissed(View v) { 145 mLinearLayout.removeView(v); 146 mCallback.handleSwipe(v); 147 } 148 149 public void onBeginDrag(View v) { 150 // We do this so the underlying ScrollView knows that it won't get 151 // the chance to intercept events anymore 152 requestDisallowInterceptTouchEvent(true); 153 v.setActivated(true); 154 } 155 156 public void onDragCancelled(View v) { 157 v.setActivated(false); 158 } 159 160 public View getChildAtPosition(MotionEvent ev) { 161 final float x = ev.getX() + getScrollX(); 162 final float y = ev.getY() + getScrollY(); 163 for (int i = 0; i < mLinearLayout.getChildCount(); i++) { 164 View item = mLinearLayout.getChildAt(i); 165 if (x >= item.getLeft() && x < item.getRight() 166 && y >= item.getTop() && y < item.getBottom()) { 167 return item; 168 } 169 } 170 return null; 171 } 172 173 public View getChildContentView(View v) { 174 return v.findViewById(R.id.recent_item); 175 } 176 177 @Override 178 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 179 super.onLayout(changed, left, top, right, bottom); 180 if (mPerformanceHelper != null) { 181 mPerformanceHelper.onLayoutCallback(); 182 } 183 } 184 185 @Override 186 public void draw(Canvas canvas) { 187 super.draw(canvas); 188 189 if (mPerformanceHelper != null) { 190 int paddingLeft = mPaddingLeft; 191 final boolean offsetRequired = isPaddingOffsetRequired(); 192 if (offsetRequired) { 193 paddingLeft += getLeftPaddingOffset(); 194 } 195 196 int left = mScrollX + paddingLeft; 197 int right = left + mRight - mLeft - mPaddingRight - paddingLeft; 198 int top = mScrollY + getFadeTop(offsetRequired); 199 int bottom = top + getFadeHeight(offsetRequired); 200 201 if (offsetRequired) { 202 right += getRightPaddingOffset(); 203 bottom += getBottomPaddingOffset(); 204 } 205 mPerformanceHelper.drawCallback(canvas, 206 left, right, top, bottom, mScrollX, mScrollY, 207 0, 0, 208 getLeftFadingEdgeStrength(), getRightFadingEdgeStrength()); 209 } 210 } 211 212 @Override 213 public int getVerticalFadingEdgeLength() { 214 if (mPerformanceHelper != null) { 215 return mPerformanceHelper.getVerticalFadingEdgeLengthCallback(); 216 } else { 217 return super.getVerticalFadingEdgeLength(); 218 } 219 } 220 221 @Override 222 public int getHorizontalFadingEdgeLength() { 223 if (mPerformanceHelper != null) { 224 return mPerformanceHelper.getHorizontalFadingEdgeLengthCallback(); 225 } else { 226 return super.getHorizontalFadingEdgeLength(); 227 } 228 } 229 230 @Override 231 protected void onFinishInflate() { 232 super.onFinishInflate(); 233 setScrollbarFadingEnabled(true); 234 mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout); 235 final int leftPadding = mContext.getResources() 236 .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin); 237 setOverScrollEffectPadding(leftPadding, 0); 238 } 239 240 @Override 241 public void onAttachedToWindow() { 242 if (mPerformanceHelper != null) { 243 mPerformanceHelper.onAttachedToWindowCallback( 244 mCallback, mLinearLayout, isHardwareAccelerated()); 245 } 246 } 247 248 @Override 249 protected void onConfigurationChanged(Configuration newConfig) { 250 super.onConfigurationChanged(newConfig); 251 float densityScale = getResources().getDisplayMetrics().density; 252 mSwipeHelper.setDensityScale(densityScale); 253 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); 254 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 255 } 256 257 private void setOverScrollEffectPadding(int leftPadding, int i) { 258 // TODO Add to (Vertical)ScrollView 259 } 260 261 @Override 262 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 263 super.onSizeChanged(w, h, oldw, oldh); 264 265 // Skip this work if a transition is running; it sets the scroll values independently 266 // and should not have those animated values clobbered by this logic 267 LayoutTransition transition = mLinearLayout.getLayoutTransition(); 268 if (transition != null && transition.isRunning()) { 269 return; 270 } 271 // Keep track of the last visible item in the list so we can restore it 272 // to the bottom when the orientation changes. 273 mLastScrollPosition = scrollPositionOfMostRecent(); 274 275 // This has to happen post-layout, so run it "in the future" 276 post(new Runnable() { 277 public void run() { 278 // Make sure we're still not clobbering the transition-set values, since this 279 // runnable launches asynchronously 280 LayoutTransition transition = mLinearLayout.getLayoutTransition(); 281 if (transition == null || !transition.isRunning()) { 282 scrollTo(mLastScrollPosition, 0); 283 } 284 } 285 }); 286 } 287 288 public void onRecentsVisibilityChanged() { 289 if (mPerformanceHelper != null) { 290 mPerformanceHelper.updateShowBackground(); 291 } 292 } 293 294 @Override 295 protected void onVisibilityChanged(View changedView, int visibility) { 296 super.onVisibilityChanged(changedView, visibility); 297 // scroll to bottom after reloading 298 if (visibility == View.VISIBLE && changedView == this) { 299 post(new Runnable() { 300 public void run() { 301 update(); 302 } 303 }); 304 } 305 } 306 307 public void setAdapter(TaskDescriptionAdapter adapter) { 308 mAdapter = adapter; 309 mAdapter.registerDataSetObserver(new DataSetObserver() { 310 public void onChanged() { 311 update(); 312 } 313 314 public void onInvalidated() { 315 update(); 316 } 317 }); 318 } 319 320 @Override 321 public void setLayoutTransition(LayoutTransition transition) { 322 if (mPerformanceHelper != null) { 323 mPerformanceHelper.setLayoutTransitionCallback(transition); 324 } 325 // The layout transition applies to our embedded LinearLayout 326 mLinearLayout.setLayoutTransition(transition); 327 } 328 329 public void setCallback(RecentsCallback callback) { 330 mCallback = callback; 331 } 332} 333