1 /*
2  * Copyright (C) 2018 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 com.android.customization.widget;
17 
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.content.res.TypedArray;
21 import android.database.DataSetObserver;
22 import android.util.AttributeSet;
23 import android.util.DisplayMetrics;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.widget.LinearLayout;
27 
28 import androidx.annotation.Nullable;
29 import androidx.core.text.TextUtilsCompat;
30 import androidx.core.view.ViewCompat;
31 import androidx.viewpager.widget.PagerAdapter;
32 import androidx.viewpager.widget.ViewPager;
33 import androidx.viewpager.widget.ViewPager.OnPageChangeListener;
34 
35 import com.android.wallpaper.R;
36 
37 import java.util.Locale;
38 
39 /**
40  * A Widget consisting of a ViewPager linked to a PageIndicator and previous/next arrows that can be
41  * used to page over that ViewPager.
42  * To use it, set a {@link PagerAdapter} using {@link #setAdapter(PagerAdapter)}, and optionally use
43  * a {@link #setOnPageChangeListener(OnPageChangeListener)} to listen for page changes.
44  */
45 public class PreviewPager extends LinearLayout {
46 
47     private static final int STYLE_PEEKING = 0;
48     private static final int STYLE_ASPECT_RATIO = 1;
49 
50     private final ViewPager mViewPager;
51     private final PageIndicator mPageIndicator;
52     private final View mPreviousArrow;
53     private final View mNextArrow;
54     private final ViewPager.OnPageChangeListener mPageListener;
55     private int mPageStyle;
56 
57     private PagerAdapter mAdapter;
58     private ViewPager.OnPageChangeListener mExternalPageListener;
59     private float mScreenAspectRatio;
60 
PreviewPager(Context context)61     public PreviewPager(Context context) {
62         this(context, null);
63     }
64 
PreviewPager(Context context, AttributeSet attrs)65     public PreviewPager(Context context, AttributeSet attrs) {
66         this(context, attrs, 0);
67     }
68 
PreviewPager(Context context, AttributeSet attrs, int defStyleAttr)69     public PreviewPager(Context context, AttributeSet attrs, int defStyleAttr) {
70         super(context, attrs, defStyleAttr);
71         LayoutInflater.from(context).inflate(R.layout.preview_pager, this);
72         Resources res = context.getResources();
73         TypedArray a = context.obtainStyledAttributes(attrs,
74                 R.styleable.PreviewPager, defStyleAttr, 0);
75 
76         mPageStyle = a.getInteger(R.styleable.PreviewPager_card_style, STYLE_PEEKING);
77 
78         a.recycle();
79 
80         mViewPager = findViewById(R.id.preview_viewpager);
81         mViewPager.setPageMargin(res.getDimensionPixelOffset(R.dimen.preview_page_gap));
82         mViewPager.setClipToPadding(false);
83         if (mPageStyle == STYLE_PEEKING) {
84             int screenWidth = mViewPager.getResources().getDisplayMetrics().widthPixels;
85             int hMargin = res.getDimensionPixelOffset(R.dimen.preview_page_horizontal_margin);
86             hMargin = Math.max(hMargin, screenWidth/8);
87             mViewPager.setPadding(
88                     hMargin,
89                     res.getDimensionPixelOffset(R.dimen.preview_page_top_margin),
90                     hMargin,
91                     res.getDimensionPixelOffset(R.dimen.preview_page_bottom_margin));
92         } else if (mPageStyle == STYLE_ASPECT_RATIO) {
93             DisplayMetrics dm = res.getDisplayMetrics();
94             mScreenAspectRatio = dm.heightPixels > dm.widthPixels
95                     ? (float) dm.heightPixels / dm.widthPixels
96                     : (float) dm.widthPixels / dm.heightPixels;
97             mViewPager.setPadding(
98                     0,
99                     res.getDimensionPixelOffset(R.dimen.preview_page_top_margin),
100                     0,
101                     res.getDimensionPixelOffset(R.dimen.preview_page_bottom_margin));
102         }
103         mPageIndicator = findViewById(R.id.page_indicator);
104         mPreviousArrow = findViewById(R.id.arrow_previous);
105         mPreviousArrow.setOnClickListener(v -> {
106             final int previousPos = mViewPager.getCurrentItem() - 1;
107             mViewPager.setCurrentItem(previousPos, true);
108         });
109         mNextArrow = findViewById(R.id.arrow_next);
110         mNextArrow.setOnClickListener(v -> {
111             final int NextPos = mViewPager.getCurrentItem() + 1;
112             mViewPager.setCurrentItem(NextPos, true);
113         });
114         mPageListener = createPageListener();
115         mViewPager.addOnPageChangeListener(mPageListener);
116     }
117 
118     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)119     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
120         if (mPageStyle == STYLE_ASPECT_RATIO) {
121             int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
122             int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
123             int indicatorHeight = mPageIndicator.getVisibility() == VISIBLE
124                     ? ((View) mPageIndicator.getParent()).getLayoutParams().height
125                     : 0;
126             int pagerHeight = availableHeight - indicatorHeight;
127             if (availableWidth > 0) {
128                 int absoluteCardWidth = (int) ((pagerHeight - mViewPager.getPaddingBottom()
129                         - mViewPager.getPaddingTop())/ mScreenAspectRatio);
130                 int hPadding = (availableWidth / 2) - (absoluteCardWidth / 2);
131                 mViewPager.setPaddingRelative(
132                         hPadding,
133                         mViewPager.getPaddingTop(),
134                         hPadding,
135                         mViewPager.getPaddingBottom());
136             }
137         }
138         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
139     }
140 
forceCardWidth(int widthPixels)141     public void forceCardWidth(int widthPixels) {
142         mViewPager.addOnLayoutChangeListener(new OnLayoutChangeListener() {
143             @Override
144             public void onLayoutChange(View v, int left, int top, int right, int bottom,
145                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
146                 int hPadding = (mViewPager.getWidth() - widthPixels) / 2;
147                 mViewPager.setPadding(hPadding, mViewPager.getPaddingTop(),
148                         hPadding, mViewPager.getPaddingBottom());
149                 mViewPager.removeOnLayoutChangeListener(this);
150             }
151         });
152         mViewPager.invalidate();
153     }
154 
155     /**
156      * Call this method to set the {@link PagerAdapter} backing the {@link ViewPager} in this
157      * widget.
158      */
setAdapter(PagerAdapter adapter)159     public void setAdapter(PagerAdapter adapter) {
160         int initialPage = 0;
161         if (mViewPager.getAdapter() != null) {
162             initialPage = isRtl() ? mAdapter.getCount() - 1 - mViewPager.getCurrentItem()
163                     : mViewPager.getCurrentItem();
164         }
165         mAdapter = adapter;
166         mViewPager.setAdapter(adapter);
167         mViewPager.setCurrentItem(isRtl() ? mAdapter.getCount() - 1 - initialPage : initialPage);
168         mAdapter.registerDataSetObserver(new DataSetObserver() {
169             @Override
170             public void onChanged() {
171                 initIndicator();
172             }
173         });
174         initIndicator();
175         updateIndicator(mViewPager.getCurrentItem());
176     }
177 
isRtl()178     private boolean isRtl() {
179         if (ViewCompat.isLayoutDirectionResolved(mViewPager)) {
180             return ViewCompat.getLayoutDirection(mViewPager) == ViewCompat.LAYOUT_DIRECTION_RTL;
181         }
182         return TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
183                 == ViewCompat.LAYOUT_DIRECTION_RTL;
184     }
185 
186     /**
187      * Set a {@link OnPageChangeListener} to be notified when the ViewPager's page state changes
188      */
setOnPageChangeListener(@ullable ViewPager.OnPageChangeListener listener)189     public void setOnPageChangeListener(@Nullable ViewPager.OnPageChangeListener listener) {
190         mExternalPageListener = listener;
191     }
192 
initIndicator()193     private void initIndicator() {
194         mPageIndicator.setNumPages(mAdapter.getCount());
195         mPageIndicator.setLocation(mViewPager.getCurrentItem());
196     }
197 
createPageListener()198     private ViewPager.OnPageChangeListener createPageListener() {
199         return new ViewPager.OnPageChangeListener() {
200              @Override
201              public void onPageScrolled(
202                      int position, float positionOffset, int positionOffsetPixels) {
203                  // For certain sizes, positionOffset never makes it to 1, so round it as we don't
204                  // need that much precision
205                  float location = (float) Math.round((position + positionOffset) * 100) / 100;
206                  mPageIndicator.setLocation(location);
207                  if (mExternalPageListener != null) {
208                      mExternalPageListener.onPageScrolled(position, positionOffset,
209                              positionOffsetPixels);
210                  }
211              }
212 
213              @Override
214              public void onPageSelected(int position) {
215                  int adapterCount = mAdapter.getCount();
216                  if (position < 0 || position >= adapterCount) {
217                      return;
218                  }
219 
220                  updateIndicator(position);
221                  if (mExternalPageListener != null) {
222                      mExternalPageListener.onPageSelected(position);
223                  }
224              }
225 
226              @Override
227              public void onPageScrollStateChanged(int state) {
228                  if (mExternalPageListener != null) {
229                      mExternalPageListener.onPageScrollStateChanged(state);
230                  }
231              }
232         };
233     }
234 
235     private void updateIndicator(int position) {
236         int adapterCount = mAdapter.getCount();
237         if (adapterCount > 1) {
238             mPreviousArrow.setVisibility(position != 0 ? View.VISIBLE : View.GONE);
239             mNextArrow.setVisibility(position != (adapterCount - 1) ? View.VISIBLE : View.GONE);
240         } else {
241             mPageIndicator.setVisibility(View.GONE);
242             mPreviousArrow.setVisibility(View.GONE);
243             mNextArrow.setVisibility(View.GONE);
244         }
245     }
246 }
247