1 /* 2 * Copyright (C) 2017 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 17 package com.android.dialer.contactsfragment; 18 19 import android.content.Context; 20 import android.support.annotation.NonNull; 21 import android.support.v7.widget.LinearLayoutManager; 22 import android.support.v7.widget.RecyclerView; 23 import android.util.AttributeSet; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.widget.RelativeLayout; 27 import android.widget.TextView; 28 29 /** Widget to add fast scrolling to {@link ContactsFragment}. */ 30 public class FastScroller extends RelativeLayout { 31 32 private final int touchTargetWidth; 33 34 private ContactsAdapter adapter; 35 private LinearLayoutManager layoutManager; 36 37 private TextView container; 38 private View scrollBar; 39 40 private boolean dragStarted; 41 FastScroller(Context context, AttributeSet attrs)42 public FastScroller(Context context, AttributeSet attrs) { 43 super(context, attrs); 44 touchTargetWidth = 45 context.getResources().getDimensionPixelSize(R.dimen.fast_scroller_touch_target_width); 46 } 47 48 @Override onFinishInflate()49 protected void onFinishInflate() { 50 super.onFinishInflate(); 51 container = findViewById(R.id.fast_scroller_container); 52 scrollBar = findViewById(R.id.fast_scroller_scroll_bar); 53 } 54 setup(ContactsAdapter adapter, LinearLayoutManager layoutManager)55 void setup(ContactsAdapter adapter, LinearLayoutManager layoutManager) { 56 this.adapter = adapter; 57 this.layoutManager = layoutManager; 58 setVisibility(VISIBLE); 59 } 60 61 @Override onTouchEvent(@onNull MotionEvent event)62 public boolean onTouchEvent(@NonNull MotionEvent event) { 63 // Don't override if touch event isn't within desired touch target and dragging hasn't started. 64 if (!dragStarted && getWidth() - touchTargetWidth - event.getX() > 0) { 65 return super.onTouchEvent(event); 66 } 67 68 switch (event.getAction()) { 69 case MotionEvent.ACTION_DOWN: 70 dragStarted = true; 71 container.setVisibility(VISIBLE); 72 scrollBar.setSelected(true); 73 // fall through 74 case MotionEvent.ACTION_MOVE: 75 setContainerAndScrollBarPosition(event.getY()); 76 setRecyclerViewPosition(event.getY()); 77 return true; 78 case MotionEvent.ACTION_UP: 79 case MotionEvent.ACTION_CANCEL: 80 dragStarted = false; 81 container.setVisibility(INVISIBLE); 82 scrollBar.setSelected(false); 83 return true; 84 default: 85 return super.onTouchEvent(event); 86 } 87 } 88 isDragStarted()89 public boolean isDragStarted() { 90 return dragStarted; 91 } 92 setRecyclerViewPosition(float y)93 private void setRecyclerViewPosition(float y) { 94 final int itemCount = adapter.getItemCount(); 95 float scrolledPosition = getScrolledPercentage(y) * (float) itemCount; 96 int targetPos = getValueInRange(0, itemCount - 1, (int) scrolledPosition); 97 layoutManager.scrollToPositionWithOffset(targetPos, 0); 98 container.setText(adapter.getHeaderString(targetPos)); 99 adapter.refreshHeaders(); 100 } 101 102 // Returns a float in range [0, 1] which represents the position of the scroller. getScrolledPercentage(float y)103 private float getScrolledPercentage(float y) { 104 if (scrollBar.getY() == 0) { 105 return 0f; 106 } else if (scrollBar.getY() + scrollBar.getHeight() >= getHeight()) { 107 return 1f; 108 } else { 109 return y / (float) getHeight(); 110 } 111 } 112 getValueInRange(int min, int max, int value)113 private int getValueInRange(int min, int max, int value) { 114 int minimum = Math.max(min, value); 115 return Math.min(minimum, max); 116 } 117 updateContainerAndScrollBarPosition(RecyclerView recyclerView)118 void updateContainerAndScrollBarPosition(RecyclerView recyclerView) { 119 if (!scrollBar.isSelected()) { 120 int verticalScrollOffset = recyclerView.computeVerticalScrollOffset(); 121 int verticalScrollRange = recyclerView.computeVerticalScrollRange(); 122 float proportion = (float) verticalScrollOffset / ((float) verticalScrollRange - getHeight()); 123 setContainerAndScrollBarPosition(getHeight() * proportion); 124 } 125 } 126 setContainerAndScrollBarPosition(float y)127 private void setContainerAndScrollBarPosition(float y) { 128 int scrollBarHeight = scrollBar.getHeight(); 129 int containerHeight = container.getHeight(); 130 scrollBar.setY( 131 getValueInRange(0, getHeight() - scrollBarHeight, (int) (y - scrollBarHeight / 2))); 132 container.setY( 133 getValueInRange( 134 0, getHeight() - containerHeight - scrollBarHeight / 2, (int) (y - containerHeight))); 135 } 136 } 137