Java tutorial
/* * Copyright (c) 2015 Eneim Labs. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations * under the License. */ package im.ene.lab.design.widget.swipecards; import android.content.Context; import android.content.res.TypedArray; import android.database.DataSetObserver; import android.graphics.PointF; import android.support.v4.view.ViewCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.Adapter; import im.ene.lab.design.R; import im.ene.lab.design.helper.Util; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; /** * Created by dionysis_lorentzos on 5/8/14 * for package com.lorentzos.swipecards * and project Swipe cards. * Use with caution dinosaurs might appear! */ public class TinderView extends BaseFlingAdapterView { static final String WIDGET_PACKAGE_NAME = TinderView.class.getPackage().getName(); static final ThreadLocal<Map<String, Constructor<TinderView.Style>>> sConstructors; static final Class<?>[] CONSTRUCTOR_PARAMS; static { CONSTRUCTOR_PARAMS = new Class[] { Context.class, AttributeSet.class }; sConstructors = new ThreadLocal<>(); } private final Style mStyle; private int MAX_VISIBLE = 4; private int MIN_ADAPTER_STACK = 6; private float ROTATION_DEGREES = 15.f; private int LAST_OBJECT_IN_STACK = 0; private Adapter mAdapter; private OnSwipeListener mFlingListener; private AdapterDataSetObserver mDataSetObserver; private boolean isInLayout = false; private View mTopView = null; private OnItemClickListener mOnItemClickListener; private OnTopViewTouchListener onTopViewTouchListener; private PointF mLastTouchPoint; public TinderView(Context context) { this(context, null); } public TinderView(Context context, AttributeSet attrs) { this(context, attrs, R.attr.swipeStyle); } public TinderView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StackView, defStyle, 0); MAX_VISIBLE = a.getInt(R.styleable.StackView_max_visible_children, MAX_VISIBLE); MIN_ADAPTER_STACK = a.getInt(R.styleable.StackView_min_adapter_stack, MIN_ADAPTER_STACK); ROTATION_DEGREES = a.getFloat(R.styleable.StackView_rotation_degrees, ROTATION_DEGREES); String layoutStyle = a.getString(R.styleable.StackView_layout_style); a.recycle(); Style style = parseStyle(context, attrs, layoutStyle); mStyle = style == null ? new SimpleStyle() : style; } // learn from CoordinatorLayout private static Style parseStyle(Context context, AttributeSet attrs, String name) { if (TextUtils.isEmpty(name)) { return null; } else { String fullName; if (name.startsWith(".")) { fullName = context.getPackageName() + name; } else if (name.indexOf(46) >= 0) { fullName = name; } else { fullName = WIDGET_PACKAGE_NAME + '.' + name; } try { Map e = (Map) sConstructors.get(); if (e == null) { e = new HashMap(); sConstructors.set(e); } Constructor c = (Constructor) e.get(fullName); if (c == null) { Class clazz = Class.forName(fullName, true, context.getClassLoader()); c = clazz.getConstructor(CONSTRUCTOR_PARAMS); c.setAccessible(true); e.put(fullName, c); } return (Style) c.newInstance(context, attrs); } catch (Exception var7) { throw new RuntimeException("Could not inflate Style subclass " + fullName, var7); } } } /** * A shortcut method to set both the listeners and the adapter. * * @param context The activity context which extends onFlingListener, OnItemClickListener or * both * @param mAdapter The adapter you have to set. */ public void init(final Context context, Adapter mAdapter) { if (context instanceof OnSwipeListener) { mFlingListener = (OnSwipeListener) context; } else { throw new RuntimeException("Activity does not implement SwipeFlingAdapterView" + ".onFlingListener"); } if (context instanceof OnItemClickListener) { mOnItemClickListener = (OnItemClickListener) context; } setAdapter(mAdapter); } @Override public View getTopView() { return mTopView; } @Override public void requestLayout() { if (!isInLayout) { super.requestLayout(); } } public OnTopViewTouchListener getTopCardListener() throws NullPointerException { if (onTopViewTouchListener == null) { throw new NullPointerException(); } return onTopViewTouchListener; } public void setMaxVisibleChildren(int MAX_VISIBLE) { this.MAX_VISIBLE = MAX_VISIBLE; } public void setMinStackInAdapter(int MIN_ADAPTER_STACK) { this.MIN_ADAPTER_STACK = MIN_ADAPTER_STACK; } public void setFlingListener(OnSwipeListener onSwipeListener) { this.mFlingListener = onSwipeListener; } public void setOnItemClickListener(OnItemClickListener onItemClickListener) { this.mOnItemClickListener = onItemClickListener; } @Override public Adapter getAdapter() { return mAdapter; } @Override public void setAdapter(Adapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); mDataSetObserver = null; } mAdapter = adapter; if (mAdapter != null && mDataSetObserver == null) { mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); } } public interface OnSwipeListener { void onTopExited(View view); void onExitToLeft(View view); void onExitToRight(View view); void onAdapterAboutToEmpty(int itemsInAdapter); void onFlingTopView(View view, float offset); } /** * A helper class which enable user to customize children's behavior. * <p/> * We focus on swiping top view behavior and the appearance of new children */ public abstract static class Style { public Style() { } public Style(Context context, AttributeSet attrs) { } /** * Override this to change the child laying out style * * @param child : child need to be layout * @param index : child's index * @param maxIndex : current stack's max index */ protected void layoutNewChild(View child, int index, int maxIndex) { } /** * Override this to change the top view swiping behavior * * @param offset : swiping offset, absolute value (0 .. 1) * @param topIndex : stack's top view index */ protected void flingTopChild(float offset, int topIndex, View otherView, int otherViewIndex) { } } public static class TinderStyle extends Style { public Context mContext; public TinderStyle() { throw new IllegalArgumentException("This Style require a Context instance"); } public TinderStyle(Context context, AttributeSet attrs) { super(context, attrs); this.mContext = context; } @Override protected void layoutNewChild(View child, int index, int maxIndex) { float scaleFactor = index < maxIndex ? 1 - 0.05f * index : 1 - 0.05f * (index - 1); ViewCompat.setScaleX(child, scaleFactor); ViewCompat.setScaleY(child, scaleFactor); if (index < maxIndex) ViewCompat.setTranslationY(child, -Util.dpToPx(mContext, 24) * index); else { ViewCompat.setTranslationY(child, 0); } } @Override protected void flingTopChild(float offset, int topIndex, View child, int childIndex) { float minScaleFactor = 1 - 0.05f * childIndex; float maxScaleFactor = 1 - 0.05f * (childIndex - 1); float scaleFactor = minScaleFactor * (1 - offset) + maxScaleFactor * offset; float fromTransY = childIndex < topIndex ? -Util.dpToPx(mContext, 24) * childIndex : 0.0f; float toTransY = -Util.dpToPx(mContext, 24) * (childIndex - 1); float transY = fromTransY * (1 - offset) + toTransY * offset; if (childIndex < topIndex) { ViewCompat.setScaleX(child, scaleFactor); ViewCompat.setScaleY(child, scaleFactor); } ViewCompat.setTranslationY(child, transY); } } /** * a Styling which just do nothing */ public class SimpleStyle extends Style { public SimpleStyle() { } public SimpleStyle(Context context, AttributeSet attrs) { super(context, attrs); } } private class AdapterDataSetObserver extends DataSetObserver { @Override public void onChanged() { requestLayout(); } @Override public void onInvalidated() { requestLayout(); } } }