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