1 /*
2  * Copyright (C) 2015 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 package android.support.car.ui;
17 
18 import android.content.Context;
19 import android.graphics.PorterDuff;
20 import android.util.AttributeSet;
21 import android.view.LayoutInflater;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.view.animation.AccelerateDecelerateInterpolator;
25 import android.view.animation.Interpolator;
26 import android.widget.FrameLayout;
27 import android.widget.ImageView;
28 
29 /**
30  * A custom view to provide list scroll behaviour -- up/down buttons and scroll indicator.
31  */
32 public class PagedScrollBarView extends FrameLayout
33         implements View.OnClickListener, View.OnLongClickListener {
34     private static final float BUTTON_DISABLED_ALPHA = 0.2f;
35 
36     /**
37      * Listener for when the list should paginate.
38      */
39     public interface PaginationListener {
40         int PAGE_UP = 0;
41         int PAGE_DOWN = 1;
42 
43         /** Called when the linked view should be paged in the given direction */
onPaginate(int direction)44         void onPaginate(int direction);
45     }
46 
47     private final ImageView mUpButton;
48     private final ImageView mDownButton;
49     private final ImageView mScrollThumb;
50     /** The "filler" view between the up and down buttons */
51     private final View mFiller;
52     private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator();
53     private final int mMinThumbLength;
54     private final int mMaxThumbLength;
55     private PaginationListener mPaginationListener;
56 
PagedScrollBarView( Context context, AttributeSet attrs)57     public PagedScrollBarView(
58             Context context, AttributeSet attrs) {
59         this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/);
60     }
61 
PagedScrollBarView( Context context, AttributeSet attrs, int defStyleAttrs)62     public PagedScrollBarView(
63             Context context, AttributeSet attrs, int defStyleAttrs) {
64         this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/);
65     }
66 
PagedScrollBarView( Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes)67     public PagedScrollBarView(
68             Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
69         super(context, attrs, defStyleAttrs, defStyleRes);
70 
71         LayoutInflater inflater = (LayoutInflater) context
72                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
73         inflater.inflate(
74                 R.layout.car_paged_scrollbar_buttons, this /*root*/, true /*attachToRoot*/);
75 
76         mUpButton = (ImageView) findViewById(R.id.page_up);
77         mUpButton.setImageDrawable(CarUiResourceLoader.getDrawable(context, "ic_up"));
78         mUpButton.setOnClickListener(this);
79         mUpButton.setOnLongClickListener(this);
80         mDownButton = (ImageView) findViewById(R.id.page_down);
81         mDownButton.setImageDrawable(CarUiResourceLoader.getDrawable(context, "ic_down"));
82         mDownButton.setOnClickListener(this);
83         mDownButton.setOnLongClickListener(this);
84 
85         mScrollThumb = (ImageView) findViewById(R.id.scrollbar_thumb);
86         mFiller = findViewById(R.id.filler);
87 
88         mMinThumbLength = getResources().getDimensionPixelSize(R.dimen.min_thumb_height);
89         mMaxThumbLength = getResources().getDimensionPixelSize(R.dimen.max_thumb_height);
90 
91         if (!context.getResources().getBoolean(R.bool.car_true_for_touch)) {
92             // Don't show the pagination buttons if there isn't touch.
93             mUpButton.setVisibility(View.GONE);
94             mDownButton.setVisibility(View.GONE);
95         }
96     }
97 
98     @Override
onClick(View v)99     public void onClick(View v) {
100         dispatchPageClick(v);
101     }
102 
103     @Override
onLongClick(View v)104     public boolean onLongClick(View v) {
105         dispatchPageClick(v);
106         return true;
107     }
108 
setPaginationListener(PaginationListener listener)109     public void setPaginationListener(PaginationListener listener) {
110         mPaginationListener = listener;
111     }
112 
113     /** Returns {@code true} if the "up" button is pressed */
isUpPressed()114     public boolean isUpPressed() {
115         return mUpButton.isPressed();
116     }
117 
118     /** Returns {@code true} if the "down" button is pressed */
isDownPressed()119     public boolean isDownPressed() {
120         return mDownButton.isPressed();
121     }
122 
123     /** Sets the range, offset and extent of the scroll bar. See {@link android.view.View}. */
setParameters(int range, int offset, int extent, boolean animate)124     protected void setParameters(int range, int offset, int extent, boolean animate) {
125         final int size = mFiller.getHeight() - mFiller.getPaddingTop() - mFiller.getPaddingBottom();
126 
127         int thumbLength = extent * size / range;
128         thumbLength = Math.max(Math.min(thumbLength, mMaxThumbLength), mMinThumbLength);
129 
130         int thumbOffset = size - thumbLength;
131         if (isDownEnabled()) {
132             // We need to adjust the offset so that it fits into the possible space inside the
133             // filler with regarding to the constraints set by mMaxThumbLength and mMinThumbLength.
134             thumbOffset = (size - thumbLength) * offset / range;
135         }
136 
137         // Sets the size of the thumb and request a redraw if needed.
138         final ViewGroup.LayoutParams lp = mScrollThumb.getLayoutParams();
139         if (lp.height != thumbLength) {
140             lp.height = thumbLength;
141             mScrollThumb.requestLayout();
142         }
143 
144         moveY(mScrollThumb, thumbOffset, animate);
145     }
146 
147     /** Sets auto day/night mode */
setAutoDayNightMode()148     protected void setAutoDayNightMode() {
149         int color = getResources().getColor(R.color.car_tint);
150         mUpButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
151         mUpButton.setBackgroundResource(R.drawable.car_pagination_background);
152         mDownButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
153         mDownButton.setBackgroundResource(R.drawable.car_pagination_background);
154     }
155 
156     /** Sets auto light mode */
setLightMode()157     protected void setLightMode() {
158         int color = getResources().getColor(R.color.car_tint_light);
159         mUpButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
160         mUpButton.setBackgroundResource(R.drawable.car_pagination_background_light);
161         mDownButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
162         mDownButton.setBackgroundResource(R.drawable.car_pagination_background_light);
163     }
164 
165     /** Sets auto dark mode */
setDarkMode()166     protected void setDarkMode() {
167         int color = getResources().getColor(R.color.car_tint_dark);
168         mUpButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
169         mUpButton.setBackgroundResource(R.drawable.car_pagination_background_dark);
170         mDownButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
171         mDownButton.setBackgroundResource(R.drawable.car_pagination_background_dark);
172     }
173 
setUpEnabled(boolean enabled)174     protected void setUpEnabled(boolean enabled) {
175         mUpButton.setEnabled(enabled);
176         mUpButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA);
177     }
178 
setDownEnabled(boolean enabled)179     protected void setDownEnabled(boolean enabled) {
180         mDownButton.setEnabled(enabled);
181         mDownButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA);
182     }
183 
isDownEnabled()184     protected boolean isDownEnabled() {
185         return mDownButton.isEnabled();
186     }
187 
dispatchPageClick(View v)188     private void dispatchPageClick(View v) {
189         final PaginationListener listener = mPaginationListener;
190         if (listener == null) {
191             return;
192         }
193 
194         final int direction = (v.getId() == R.id.page_up)
195                 ? PaginationListener.PAGE_UP : PaginationListener.PAGE_DOWN;
196         listener.onPaginate(direction);
197     }
198 
199     /** Moves the given view to the specified 'y' position. */
moveY(final View view, float newPosition, boolean animate)200     private void moveY(final View view, float newPosition, boolean animate) {
201         final int duration = animate ? 200 : 0;
202         view.animate()
203                 .y(newPosition)
204                 .setDuration(duration)
205                 .setInterpolator(mPaginationInterpolator)
206                 .start();
207     }
208 }