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