Home | History | Annotate | Download | only in calculator2
      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 
     17 package com.android.calculator2;
     18 
     19 import android.content.Context;
     20 import android.graphics.Color;
     21 import android.support.v4.view.PagerAdapter;
     22 import android.support.v4.view.ViewPager;
     23 import android.util.AttributeSet;
     24 import android.util.Log;
     25 import android.view.GestureDetector;
     26 import android.view.MotionEvent;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 
     30 public class CalculatorPadViewPager extends ViewPager {
     31 
     32     private final PagerAdapter mStaticPagerAdapter = new PagerAdapter() {
     33         @Override
     34         public int getCount() {
     35             return getChildCount();
     36         }
     37 
     38         @Override
     39         public View instantiateItem(ViewGroup container, final int position) {
     40             final View child = getChildAt(position);
     41 
     42             // Set a OnClickListener to scroll to item's position when it isn't the current item.
     43             child.setOnClickListener(new OnClickListener() {
     44                 @Override
     45                 public void onClick(View v) {
     46                     setCurrentItem(position, true /* smoothScroll */);
     47                 }
     48             });
     49             // Set an OnTouchListener to always return true for onTouch events so that a touch
     50             // sequence cannot pass through the item to the item below.
     51             child.setOnTouchListener(new OnTouchListener() {
     52                 @Override
     53                 public boolean onTouch(View v, MotionEvent event) {
     54                     v.onTouchEvent(event);
     55                     return true;
     56                 }
     57             });
     58 
     59             // Set an OnHoverListener to always return true for onHover events so that focus cannot
     60             // pass through the item to the item below.
     61             child.setOnHoverListener(new OnHoverListener() {
     62                 @Override
     63                 public boolean onHover(View v, MotionEvent event) {
     64                     v.onHoverEvent(event);
     65                     return true;
     66                 }
     67             });
     68             // Make the item focusable so it can be selected via a11y.
     69             child.setFocusable(true);
     70             // Set the content description of the item which will be used by a11y to identify it.
     71             child.setContentDescription(getPageTitle(position));
     72 
     73             return child;
     74         }
     75 
     76         @Override
     77         public void destroyItem(ViewGroup container, int position, Object object) {
     78             removeViewAt(position);
     79         }
     80 
     81         @Override
     82         public boolean isViewFromObject(View view, Object object) {
     83             return view == object;
     84         }
     85 
     86         @Override
     87         public float getPageWidth(int position) {
     88             return position == 1 ? 7.0f / 9.0f : 1.0f;
     89         }
     90 
     91         @Override
     92         public CharSequence getPageTitle(int position) {
     93             final String[] pageDescriptions = getContext().getResources()
     94                     .getStringArray(R.array.desc_pad_pages);
     95             return pageDescriptions[position];
     96         }
     97     };
     98 
     99     private final OnPageChangeListener mOnPageChangeListener = new SimpleOnPageChangeListener() {
    100         @Override
    101         public void onPageSelected(int position) {
    102             for (int i = getChildCount() - 1; i >= 0; --i) {
    103                 final View child = getChildAt(i);
    104                 // Only the "peeking" or covered page should be clickable.
    105                 child.setClickable(i != position);
    106 
    107                 // Prevent clicks and accessibility focus from going through to descendants of
    108                 // other pages which are covered by the current page.
    109                 if (child instanceof ViewGroup) {
    110                     final ViewGroup childViewGroup = (ViewGroup) child;
    111                     for (int j = childViewGroup.getChildCount() - 1; j >= 0; --j) {
    112                         childViewGroup.getChildAt(j)
    113                                 .setImportantForAccessibility(i == position
    114                                         ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
    115                                         : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
    116                     }
    117                 }
    118             }
    119         }
    120     };
    121 
    122     private final PageTransformer mPageTransformer = new PageTransformer() {
    123         @Override
    124         public void transformPage(View view, float position) {
    125             if (position < 0.0f) {
    126                 // Pin the left page to the left side.
    127                 view.setTranslationX(getWidth() * -position);
    128                 view.setAlpha(Math.max(1.0f + position, 0.0f));
    129             } else {
    130                 // Use the default slide transition when moving to the next page.
    131                 view.setTranslationX(0.0f);
    132                 view.setAlpha(1.0f);
    133             }
    134         }
    135     };
    136 
    137     private final GestureDetector.SimpleOnGestureListener mGestureWatcher =
    138             new GestureDetector.SimpleOnGestureListener() {
    139         @Override
    140         public boolean onDown(MotionEvent e) {
    141             // Return true so calls to onSingleTapUp are not blocked.
    142             return true;
    143         }
    144 
    145         @Override
    146         public boolean onSingleTapUp(MotionEvent ev) {
    147             if (mClickedItemIndex != -1) {
    148                 getChildAt(mClickedItemIndex).performClick();
    149                 mClickedItemIndex = -1;
    150                 return true;
    151             }
    152             return super.onSingleTapUp(ev);
    153         }
    154     };
    155 
    156     private final GestureDetector mGestureDetector;
    157 
    158     private int mClickedItemIndex = -1;
    159 
    160     public CalculatorPadViewPager(Context context) {
    161         this(context, null /* attrs */);
    162     }
    163 
    164     public CalculatorPadViewPager(Context context, AttributeSet attrs) {
    165         super(context, attrs);
    166 
    167         mGestureDetector = new GestureDetector(context, mGestureWatcher);
    168         mGestureDetector.setIsLongpressEnabled(false);
    169 
    170         setAdapter(mStaticPagerAdapter);
    171         setBackgroundColor(Color.BLACK);
    172         setPageMargin(-getResources().getDimensionPixelSize(R.dimen.pad_page_margin));
    173         setPageTransformer(false, mPageTransformer);
    174         addOnPageChangeListener(mOnPageChangeListener);
    175     }
    176 
    177     @Override
    178     protected void onFinishInflate() {
    179         super.onFinishInflate();
    180 
    181         // Invalidate the adapter's data set since children may have been added during inflation.
    182         getAdapter().notifyDataSetChanged();
    183 
    184         // Let page change listener know about our initial position.
    185         mOnPageChangeListener.onPageSelected(getCurrentItem());
    186     }
    187 
    188     @Override
    189     public boolean onInterceptTouchEvent(MotionEvent ev) {
    190         try {
    191             // Always intercept touch events when a11y focused since otherwise they will be
    192             // incorrectly offset by a11y before being dispatched to children.
    193             if (isAccessibilityFocused() || super.onInterceptTouchEvent(ev)) {
    194                 return true;
    195             }
    196 
    197             // Only allow the current item to receive touch events.
    198             final int action = ev.getActionMasked();
    199             if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
    200                 // If a child is a11y focused then we must always intercept the touch event
    201                 // since it will be incorrectly offset by a11y.
    202                 final int childCount = getChildCount();
    203                 for (int childIndex = childCount - 1; childIndex >= 0; --childIndex) {
    204                     if (getChildAt(childIndex).isAccessibilityFocused()) {
    205                         mClickedItemIndex = childIndex;
    206                         return true;
    207                     }
    208                 }
    209 
    210                 if (action == MotionEvent.ACTION_DOWN) {
    211                     mClickedItemIndex = -1;
    212                 }
    213 
    214                 // Otherwise if touch is on a non-current item then intercept.
    215                 final int actionIndex = ev.getActionIndex();
    216                 final float x = ev.getX(actionIndex) + getScrollX();
    217                 final float y = ev.getY(actionIndex) + getScrollY();
    218                 for (int i = childCount - 1; i >= 0; --i) {
    219                     final int childIndex = getChildDrawingOrder(childCount, i);
    220                     final View child = getChildAt(childIndex);
    221                     if (child.getVisibility() == VISIBLE
    222                             && x >= child.getLeft() && x < child.getRight()
    223                             && y >= child.getTop() && y < child.getBottom()) {
    224                         if (action == MotionEvent.ACTION_DOWN) {
    225                             mClickedItemIndex = childIndex;
    226                         }
    227                         return childIndex != getCurrentItem();
    228                     }
    229                 }
    230             }
    231 
    232             return false;
    233         } catch (IllegalArgumentException e) {
    234             Log.e("Calculator", "Error intercepting touch event", e);
    235             return false;
    236         }
    237     }
    238 
    239     @Override
    240     public boolean onTouchEvent(MotionEvent ev) {
    241         try {
    242             // Allow both the gesture detector and super to handle the touch event so they both see
    243             // the full sequence of events. This should be safe since the gesture detector only
    244             // handle clicks and super only handles swipes.
    245             mGestureDetector.onTouchEvent(ev);
    246             return super.onTouchEvent(ev);
    247         } catch (IllegalArgumentException e) {
    248             Log.e("Calculator", "Error processing touch event", e);
    249             return false;
    250         }
    251     }
    252 }
    253