package com.prolificinteractive.chandelier.widget; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.util.TypedValue; import android.view.MotionEvent; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Transformation; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import com.prolificinteractive.chandelier.R; import java.util.ArrayList; import java.util.List; import static android.view.Gravity.CENTER; import static android.view.Gravity.CENTER_VERTICAL; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; public class OrnamentLayout extends FrameLayout { private static final int ITEM_WEIGHT = 1; private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); private static final AccelerateDecelerateInterpolator ACCELERATE_DECELERATE_INTERPOLATOR = new AccelerateDecelerateInterpolator(); private static final int DEFAULT_SCALE = 1; private final int imageViewMargin; private final int selectedSize; private final LinearLayout container; private final ImageView selectedImageView; private final List<Ornament> ornaments = new ArrayList<>(); private final List<ImageView> imageViews = new ArrayList<>(); private final int actionItemLayoutHeight; private final int actionItemLayoutWidth; private final int shortAnimDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); private boolean isScaleEnabled; private int measuredWidth; private int selectedIndex = -1; private boolean isAnimating = false; private Animation.AnimationListener actionListener; private final Animation.AnimationListener animationListener = new SimpleAnimationListener() { @Override public void onAnimationStart(Animation animation) { imageViews.get(selectedIndex).setSelected(true); } @Override public void onAnimationEnd(Animation animation) { isAnimating = false; if (actionListener != null) { actionListener.onAnimationEnd(null); } actionListener = null; } }; public OrnamentLayout(final Context context, final AttributeSet attrs) { super(context); final Resources res = getResources(); final TypedArray a = context.getTheme().obtainStyledAttributes( attrs, R.styleable.ChandelierLayout, 0, 0); // Defaults final int defaultElevation = res.getDimensionPixelSize(R.dimen.default_elevation); final int defaultSelectorMargin = res.getDimensionPixelSize(R.dimen.default_selector_margin); final int defaultSelectorSize = res.getDimensionPixelSize(R.dimen.default_selector_size); final boolean defaultScaleEnabled = res.getBoolean(R.bool.default_scale_enabled); isScaleEnabled = a.getBoolean(R.styleable.ChandelierLayout_chandelier_scale_enabled, defaultScaleEnabled); actionItemLayoutHeight = a.getDimensionPixelSize(R.styleable.ChandelierLayout_ornament_layout_height, WRAP_CONTENT); actionItemLayoutWidth = a.getDimensionPixelSize(R.styleable.ChandelierLayout_ornament_layout_width, WRAP_CONTENT); // Action Layout container = new LinearLayout(context); container.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT, CENTER_VERTICAL)); setBackground(a.getDrawable(R.styleable.ChandelierLayout_chandelier_background)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { setElevation( a.getDimensionPixelSize(R.styleable.ChandelierLayout_chandelier_elevation, defaultElevation)); } container.setOrientation(LinearLayout.HORIZONTAL); container.setGravity(CENTER_VERTICAL); // Action Item imageViewMargin = a.getDimensionPixelSize(R.styleable.ChandelierLayout_ornament_margin, defaultSelectorMargin); selectedSize = a.getDimensionPixelSize(R.styleable.ChandelierLayout_chandelier_selected_size, defaultSelectorSize); selectedImageView = new ImageView(context); final LayoutParams selectedLp = new LayoutParams(selectedSize, selectedSize, CENTER_VERTICAL); selectedLp.setMargins(0, imageViewMargin, 0, imageViewMargin); selectedImageView.setLayoutParams(selectedLp); Drawable selectorBackground = a.getDrawable(R.styleable.ChandelierLayout_chandelier_selector); if (selectorBackground != null) { selectedImageView.setBackground(selectorBackground); } else { selectedImageView.setBackground(res.getDrawable(R.drawable.default_selector)); } Drawable layoutBackground = a.getDrawable(R.styleable.ChandelierLayout_chandelier_background); if (layoutBackground != null) { setBackground(layoutBackground); } else { TypedValue typedValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true); setBackgroundColor(typedValue.data); } a.recycle(); addView(selectedImageView); addView(container); } public void populateActionItems(int... drawablesResIds) { final ArrayList<Ornament> items = new ArrayList<>(); for (int resId : drawablesResIds) { items.add(new Ornament(resId)); } populateActionItems(items); } public void populateActionItems(@Nullable final List<? extends Ornament> items) { container.removeAllViews(); ornaments.clear(); imageViews.clear(); if (items != null) { ornaments.addAll(items); final Context context = getContext(); for (final Ornament item : items) { final FrameLayout frame = new FrameLayout(context); frame.setLayoutParams(new LinearLayout.LayoutParams( MATCH_PARENT, WRAP_CONTENT, ITEM_WEIGHT) ); final ImageView imageView = new ImageView(context); final FrameLayout.LayoutParams imageLp = new FrameLayout.LayoutParams( actionItemLayoutWidth, actionItemLayoutHeight, CENTER ); imageLp.setMargins(imageViewMargin, imageViewMargin, imageViewMargin, imageViewMargin); imageView.setLayoutParams(imageLp); imageView.setBackgroundResource(item.drawableResId); imageViews.add(imageView); frame.addView(imageView); container.addView(frame); } selectedIndex = ornaments.size() / 2; imageViews.get(selectedIndex).setSelected(true); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); this.measuredWidth = getMeasuredWidth(); } public void onParentTouchEvent(final MotionEvent ev) { if (measuredWidth <= 0 || isAnimating) { return; } final float x = ev.getX(); final int count = ornaments.size(); // One quarter of the screen width final int q = measuredWidth / 4; // projected x in center of screen final int pX = getInRange(Math.round((x - q) * 2), 0, measuredWidth); // width of one item final int iW = Math.round(measuredWidth / count); final int newSelectedIndex = getInRange(Math.round(pX / iW), 0, count - 1); // sensitivity range final int xS = iW / count; switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: if (pX < selectedIndex * iW + xS) { // left edge if (selectedIndex == 0) { break; } // actual progress final float t = getInRange((selectedIndex * iW + xS - pX) / (2f * xS), 0f, 1f); // scale final float sX = isScaleEnabled ? t / 2 + 1 : 1; // position in the middle final int mX = iW * selectedIndex + (iW - selectedSize) / 2; // interpolated progress final float it = getInRange(ACCELERATE_INTERPOLATOR.getInterpolation(t), 0f, 1f); // amount to move final int dX = Math.round(it * 2 * xS); // target final int tX = mX - dX; selectedImageView.setPivotX(0); selectedImageView.setScaleX(sX); selectedImageView.setTranslationX(tX); if (pX < iW * selectedIndex - xS) { setSelectedIndex(newSelectedIndex); } } else if (pX > (selectedIndex + 1) * iW - xS) { // right edge if (selectedIndex == count - 1) { break; } // actual progress final float t = getInRange((pX - (selectedIndex + 1) * iW + xS) / (2f * xS), 0f, 1f); // scale final float sX = isScaleEnabled ? t / 2 + 1 : 1; // position in the middle final int mX = iW * selectedIndex + (iW - selectedSize) / 2; // interpolated progress final float it = getInRange(ACCELERATE_INTERPOLATOR.getInterpolation(t), 0f, 1f); // amount to move final int dX = Math.round(it * 2 * xS); // target final int tX = mX + dX; selectedImageView.setPivotX(selectedSize); selectedImageView.setScaleX(sX); selectedImageView.setTranslationX(tX); if (pX > iW * (selectedIndex + 1) + xS) { setSelectedIndex(newSelectedIndex); } } else { // middle selectedImageView.setTranslationX(selectedIndex * iW + (iW - selectedSize) / 2); selectedImageView.setScaleX(DEFAULT_SCALE); } break; } } private void setSelectedIndex(final int newSelectedIndex) { // Un-select previous index imageViews.get(selectedIndex).setSelected(false); selectedIndex = newSelectedIndex; isAnimating = true; final int iW = measuredWidth / ornaments.size(); final int target = iW * selectedIndex + (iW - selectedSize) / 2; final float currentScale = selectedImageView.getScaleX(); final float currentTranslation = selectedImageView.getTranslationX(); final Animation animation = new Animation() { @Override protected void applyTransformation(float t, Transformation transformation) { ViewCompat.setScaleX(selectedImageView, (DEFAULT_SCALE - currentScale) * t + currentScale); ViewCompat.setTranslationX(selectedImageView, (target - currentTranslation) * t + currentTranslation); } }; animation.setAnimationListener(animationListener); animation.setDuration(shortAnimDuration); animation.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR); selectedImageView.clearAnimation(); selectedImageView.startAnimation(animation); } private int getInRange(final int value, final int min, final int max) { return Math.max(min, Math.min(max, value)); } private float getInRange(final float value, final float min, final float max) { return Math.max(min, Math.min(max, value)); } public int getSelectedIndex() { return selectedIndex; } public void finishAction(Animation.AnimationListener mActionListener) { actionListener = mActionListener; setSelectedIndex(selectedIndex); } public void onLayoutTranslated(final float progress) { } public Ornament getActionItem(int index) { return ornaments.get(index); } }