callback) {
this.callback = callback;
}
@Override
public void success(T t, Response response) {
if (callback != null) {
callback.success(t, response);
}
}
@Override
public void failure(RetrofitError error) {
if (callback != null) {
callback.failure(error);
}
}
public void detach() {
callback = null;
}
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/presenter/PresenterActivity.java
================================================
package net.avenwu.support.presenter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
/**
* Created by chaobin on 7/14/15.
*/
public abstract class PresenterActivity extends AppCompatActivity {
protected P mPresenter;
protected abstract Class extends P> getPresenterClass();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
mPresenter = getPresenterClass().newInstance();
mPresenter.attach();
} catch (java.lang.InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.detach();
}
public P getPresenter() {
return mPresenter;
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/presenter/PresenterFragment.java
================================================
package net.avenwu.support.presenter;
import android.os.Bundle;
import android.support.v4.app.Fragment;
public abstract class PresenterFragment
extends Fragment {
P mPresenter;
protected abstract Class extends P> getPresenterClass();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
mPresenter = getPresenterClass().newInstance();
mPresenter.attach();
} catch (java.lang.InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public void onDetach() {
super.onDetach();
mPresenter.detach();
}
public P getPresenter() {
return mPresenter;
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/protocol/RenderAction.java
================================================
package net.avenwu.support.protocol;
/**
* Created by chaobin on 7/14/15.
*/
public interface RenderAction {
void onUpdate(T data);
}
================================================
FILE: support/src/main/java/net/avenwu/support/protocol/UIAction.java
================================================
package net.avenwu.support.protocol;
/**
* Created by chaobin on 7/14/15.
*/
public abstract class UIAction implements RenderAction {
@Override
public void onUpdate(T data) {
}
public abstract void onUpdateUI(T data);
}
================================================
FILE: support/src/main/java/net/avenwu/support/util/BitmapUtil.java
================================================
package net.avenwu.support.util;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
/**
* Created by chaobin on 3/16/15.
*/
public class BitmapUtil {
/**
* @param bitmap image src
* @param radius ignore for circle, only used in rounded rectangle
* @param circle whether return circle or not
* @return decoded bitmap
*/
public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, float radius, boolean circle) {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int width = Math.min(bitmap.getWidth(), bitmap.getHeight());
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(Color.RED);//this color is not useful actually, the shape we drew is kind of mask
final float dx = (bitmap.getWidth() - width) / 2.0f;
final float dy = (bitmap.getHeight() - width) / 2.0f;
final RectF srcRect = new RectF(dx, dy, dx + width, dy + width);
final float r = circle ? width / 2.0f : radius;
canvas.drawRoundRect(srcRect, r, r, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/util/ChartSet.java
================================================
package net.avenwu.support.util;
import android.util.Log;
/**
* Created by chaobin on 11/22/15.
*/
public class ChartSet {
static final String TAG = ChartSet.class.getCanonicalName();
final static char ANSI_START = '!';
final static char ANSI_END = '~';
final static char UNICODE_START = '!';
final static char UNICODE_END = '~';
final static long DIFF = UNICODE_START - ANSI_START;
public static String convertDBCS(CharSequence text) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
Log.e(TAG, "" + c);
if (isCharNeedConvert(c)) {
c = convert(c);
Log.e(TAG, "translated:" + c);
}
builder.append(c);
}
return builder.toString();
}
public static boolean isCharNeedConvert(char c) {
return c >= UNICODE_START && c <= UNICODE_END;
}
public static char convert(char c) {
return (char) (c - DIFF);
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/util/Device.java
================================================
package net.avenwu.support.util;
/**
* Created by chaobin on 3/15/15.
*/
public class Device {
/**
* simple hack to avoid umeng auto update crash the app on devices not based on arm
* eg: genymotion based on i686
*/
public static boolean isARMBasedDevice() {
final String arch = System.getProperty("os.arch");
return arch.startsWith("arm") || arch.startsWith("ARM");
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/util/ReflectionUtils.java
================================================
package net.avenwu.support.util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Created by chris on 17/12/14.
* For Calligraphy.
*/
class ReflectionUtils {
static Field getField(Class clazz, String fieldName) {
try {
final Field f = clazz.getDeclaredField(fieldName);
f.setAccessible(true);
return f;
} catch (NoSuchFieldException ignored) {
}
return null;
}
static Object getValue(Field field, Object obj) {
try {
return field.get(obj);
} catch (IllegalAccessException ignored) {
}
return null;
}
static void setValue(Field field, Object obj, Object value) {
try {
field.set(obj, value);
} catch (IllegalAccessException ignored) {
}
}
static Method getMethod(Class clazz, String methodName) {
final Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
method.setAccessible(true);
return method;
}
}
return null;
}
static void invokeMethod(Object object, Method method, Object... args) {
try {
if (method == null) return;
method.invoke(object, args);
} catch (IllegalAccessException | InvocationTargetException ignored) {
ignored.printStackTrace();
}
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/util/TypefaceContextWrapper.java
================================================
package net.avenwu.support.util;
import android.content.Context;
import android.content.ContextWrapper;
import android.view.LayoutInflater;
/**
* Created by chaobin on 10/8/15.
*/
public class TypefaceContextWrapper extends ContextWrapper {
private TypefaceLayoutInflator mInflater;
public TypefaceContextWrapper(Context base) {
super(base);
}
public static ContextWrapper wrap(Context base) {
return new TypefaceContextWrapper(base);
}
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = new TypefaceLayoutInflator(LayoutInflater.from(getBaseContext()), this);
}
return mInflater;
}
return super.getSystemService(name);
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/util/TypefaceLayoutInflator.java
================================================
package net.avenwu.support.util;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.xmlpull.v1.XmlPullParser;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* modified from CalligraphyLayoutInflater(Created by chris on 19/12/2013)
*
* Created by chaobin on 10/8/15.
*/
public class TypefaceLayoutInflator extends LayoutInflater {
public TypefaceLayoutInflator(Context context) {
super(context);
}
public TypefaceLayoutInflator(LayoutInflater original, Context newContext) {
super(original, newContext);
}
@Override
public LayoutInflater cloneInContext(Context newContext) {
return new TypefaceLayoutInflator(this, newContext);
}
@Override
protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException {
View view = super.onCreateView(parent, name, attrs);
onViewCreatedInternal(view, getContext(), attrs);
return view;
}
@Override
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
View view = super.onCreateView(name, attrs);
onViewCreatedInternal(view, getContext(), attrs);
return view;
}
@Override
public void setFactory2(Factory2 factory) {
// Only set our factory and wrap calls to the Factory2 trying to be set!
if (!(factory instanceof WrapperFactory2)) {
// LayoutInflaterCompat.setFactory(this, new WrapperFactory2(factory2, mCalligraphyFactory));
super.setFactory2(new WrapperFactory2(factory));
} else {
super.setFactory2(factory);
}
}
@Override
public void setFactory(LayoutInflater.Factory factory) {
// Only set our factory and wrap calls to the Factory trying to be set!
if (!(factory instanceof WrapperFactory)) {
super.setFactory(new WrapperFactory(factory, this));
} else {
super.setFactory(factory);
}
}
@Override
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
setPrivateFactoryInternal();
return super.inflate(parser, root, attachToRoot);
}
private boolean mSetPrivateFactory = false;
private void setPrivateFactoryInternal() {
// Already tried to set the factory.
if (mSetPrivateFactory) return;
// Reflection (Or Old Device) skip.
// if (!CalligraphyConfig.get().isReflection()) return;
// Skip if not attached to an activity.
if (!(getContext() instanceof Factory2)) {
mSetPrivateFactory = true;
return;
}
final Method setPrivateFactoryMethod = ReflectionUtils
.getMethod(LayoutInflater.class, "setPrivateFactory");
if (setPrivateFactoryMethod != null) {
ReflectionUtils.invokeMethod(this,
setPrivateFactoryMethod,
new PrivateWrapperFactory2((Factory2) getContext(), this));
}
mSetPrivateFactory = true;
}
private static class WrapperFactory implements LayoutInflater.Factory {
private final Factory mFactory;
private final TypefaceLayoutInflator mInflater;
public WrapperFactory(Factory factory, TypefaceLayoutInflator inflator) {
mFactory = factory;
mInflater = inflator;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
try {
View view = mInflater.createCustomViewInternal(
null, mFactory.onCreateView(name, context, attrs), name, context, attrs);
onViewCreatedInternal(view, context, attrs);
return view;
} catch (Exception e) {
e.printStackTrace();
}
}
View view = mFactory.onCreateView(name, context, attrs);
onViewCreatedInternal(view, context, attrs);
return view;
}
}
static void onViewCreatedInternal(View view, final Context context, AttributeSet attrs) {
if (view instanceof TextView) {
TypefaceUtils.setTypeface(context, (TextView) view, "fonts/RobotoCondensed-Regular.ttf");
}
}
/**
* Nasty method to inflate custom layouts that haven't been handled else where. If this fails it
* will fall back through to the PhoneLayoutInflater method of inflating custom views where
* Calligraphy will NOT have a hook into.
*
* @param parent parent view
* @param view view if it has been inflated by this point, if this is not null this method
* just returns this value.
* @param name name of the thing to inflate.
* @param viewContext Context to inflate by if parent is null
* @param attrs Attr for this view which we can steal fontPath from too.
* @return view or the View we inflate in here.
*/
private View createCustomViewInternal(View parent, View view, String name, Context
viewContext, AttributeSet attrs) throws Exception {
// I by no means advise anyone to do this normally, but Google have locked down access to
// the createView() method, so we never get a callback with attributes at the end of the
// createViewFromTag chain (which would solve all this unnecessary rubbish).
// We at the very least try to optimise this as much as possible.
// We only call for customViews (As they are the ones that never go through onCreateView(...)).
// We also maintain the Field reference and make it accessible which will make a pretty
// significant difference to performance on Android 4.0+.
// If CustomViewCreation is off skip this.
if (view == null && name.indexOf('.') > -1) {
if (mConstructorArgs == null)
mConstructorArgs = ReflectionUtils.getField(LayoutInflater.class, "mConstructorArgs");
final Object[] mConstructorArgsArr = (Object[]) ReflectionUtils.getValue(mConstructorArgs, this);
final Object lastContext = mConstructorArgsArr[0];
// The LayoutInflater actually finds out the correct context to use. We just need to set
// it on the mConstructor for the internal method.
// Set the constructor ars up for the createView, not sure why we can't pass these in.
mConstructorArgsArr[0] = viewContext;
ReflectionUtils.setValue(mConstructorArgs, this, mConstructorArgsArr);
try {
view = createView(name, null, attrs);
} catch (ClassNotFoundException ignored) {
} finally {
mConstructorArgsArr[0] = lastContext;
ReflectionUtils.setValue(mConstructorArgs, this, mConstructorArgsArr);
}
}
return view;
}
private Field mConstructorArgs = null;
@TargetApi(11)
private static class WrapperFactory2 implements Factory2 {
protected final Factory2 mFactory2;
public WrapperFactory2(Factory2 factory2) {
mFactory2 = factory2;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
View view = mFactory2.onCreateView(name, context, attrs);
onViewCreatedInternal(view, context, attrs);
return view;
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = mFactory2.onCreateView(parent, name, context, attrs);
onViewCreatedInternal(view, context, attrs);
return view;
}
}
@TargetApi(11)
private static class PrivateWrapperFactory2 extends WrapperFactory2 {
private final TypefaceLayoutInflator mInflater;
public PrivateWrapperFactory2(Factory2 factory2, TypefaceLayoutInflator inflater) {
super(factory2);
mInflater = inflater;
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
try {
View view = mInflater.createCustomViewInternal(parent,
mFactory2.onCreateView(parent, name, context, attrs),
name, context, attrs
);
onViewCreatedInternal(view, context, attrs);
return view;
} catch (Exception e) {
e.printStackTrace();
}
return super.onCreateView(parent, name, context, attrs);
}
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/util/TypefaceUtils.java
================================================
package net.avenwu.support.util;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.Log;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
/**
* Created by chaobin on 10/8/15.
*/
public class TypefaceUtils {
private static final Map> sCachedFonts = new HashMap<>();
/**
* A helper loading a custom font.
*
* @param assetManager App's asset manager.
* @param filePath The path of the file.
* @return Return {@link android.graphics.Typeface} or null if the path is invalid.
*/
public static Typeface load(final AssetManager assetManager, final String filePath) {
synchronized (sCachedFonts) {
try {
if (!sCachedFonts.containsKey(filePath) || sCachedFonts.get(filePath).get() == null) {
final Typeface typeface = Typeface.createFromAsset(assetManager, filePath);
sCachedFonts.put(filePath, new WeakReference(typeface));
return typeface;
}
} catch (Exception e) {
Log.w("Calligraphy", "Can't create asset from " + filePath + ". Make sure you have passed in the correct path and file name.", e);
sCachedFonts.put(filePath, null);
return null;
}
return sCachedFonts.get(filePath).get();
}
}
public static void setTypeface(Context context, TextView textView, String filePath) {
if (textView == null || context == null) return;
textView.setPaintFlags(textView.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
textView.setTypeface(load(context.getAssets(), filePath));
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/util/ViewCompat.java
================================================
package net.avenwu.support.util;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorRes;
import android.support.annotation.StringRes;
import android.view.View;
import android.widget.AbsListView;
import android.widget.EdgeEffect;
import android.widget.TextView;
import android.widget.Toast;
import net.avenwu.support.R;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Created by chaobin on 2/25/15.
*/
public class ViewCompat {
/**
* set specific distance for AbsListView to enable the over scroll effect
*
* @param listView
* @param distance
*/
public static void enableOverScroll(AbsListView listView, int distance) {
try {
Field overscrollDistance = AbsListView.class.getDeclaredField("mOverscrollDistance");
overscrollDistance.setAccessible(true);
overscrollDistance.setInt(listView, distance);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* set custom edge color on over scroll
* Copy from http://stackoverflow.com/questions/11603267/scrollview-change-the-edge-effect-color-with-holo
*
* @param listView
* @param color
*/
public static void customEdgeEffectColor(AbsListView listView, @ColorRes int color) {
try {
final Field fEdgeGlowTop = AbsListView.class.getDeclaredField("mEdgeGlowTop");
final Field fEdgeGlowBottom = AbsListView.class.getDeclaredField("mEdgeGlowBottom");
fEdgeGlowTop.setAccessible(true);
fEdgeGlowBottom.setAccessible(true);
setEdgeEffectColor((EdgeEffect) fEdgeGlowTop.get(listView), color);
setEdgeEffectColor((EdgeEffect) fEdgeGlowBottom.get(listView), color);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void setEdgeEffectColor(final EdgeEffect edgeEffect, @ColorRes final int color) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
edgeEffect.setColor(color);
return;
}
final Field edgeField = EdgeEffect.class.getDeclaredField("mEdge");
final Field glowField = EdgeEffect.class.getDeclaredField("mGlow");
edgeField.setAccessible(true);
glowField.setAccessible(true);
final Drawable edge = (Drawable) edgeField.get(edgeEffect);
final Drawable glow = (Drawable) glowField.get(edgeEffect);
edge.setColorFilter(color, PorterDuff.Mode.SRC_IN);
glow.setColorFilter(color, PorterDuff.Mode.SRC_IN);
edge.setCallback(null); // free up any references
glow.setCallback(null); // free up any references
} catch (final Exception ignored) {
ignored.printStackTrace();
}
}
/**
* Remove the left space on ActionBar while using Toolbar as ActionBar
* Usage:
* {@code ViewCompat.cleanContentInset(getSupportActionBar().getCustomView());}
*
* Toolbar toolbar = (Toolbar) customView.getParent();
* toolbar.setContentInsetsAbsolute(0, 0);
*
* @param customView
*/
public static boolean removeContentInsetOfToolbar(View customView) {
if (customView != null && customView.getParent().getClass().getCanonicalName().equals("android.support.v7.widget.Toolbar")) {
try {
Method setContentInsetsAbsolute = customView.getParent().getClass().getDeclaredMethod("setContentInsetsAbsolute", int.class, int.class);
setContentInsetsAbsolute.setAccessible(true);
setContentInsetsAbsolute.invoke(customView.getParent(), 0, 0);
return true;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return false;
}
public static Toast makeText(Context context, String text, int duration) {
Toast result = new Toast(context);
View v = View.inflate(context, R.layout.custom_toast, null);
TextView tv = (TextView) v.findViewById(android.R.id.message);
tv.setText(text);
result.setView(v);
result.setDuration(duration);
return result;
}
public static Toast makeText(Context context, @StringRes int text, int duration) {
Toast result = new Toast(context);
View v = View.inflate(context, R.layout.custom_toast, null);
TextView tv = (TextView) v.findViewById(android.R.id.message);
tv.setText(text);
result.setView(v);
result.setDuration(duration);
return result;
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/BreathingDelegate.java
================================================
package net.avenwu.support.widget;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.LinearInterpolator;
import net.avenwu.support.R;
/**
* Created by chaobin on 4/6/15.
*/
@SuppressWarnings("NewApi")
public class BreathingDelegate {
private Paint mPaint;
private RectF mRippleRect = new RectF();
private float mRippleRadius;
private float mEndRadius;
private int mRippleAlpha = 0xff;
private int mDuration;
private RectF mBorderRect = new RectF();
private AnimatorSet mAnimatorSet = new AnimatorSet();
private boolean mAutoStart;
private int mRippleColor;
private View mTarget;
public BreathingDelegate(Context context, AttributeSet attrs, View view) {
mTarget = view;
mTarget.setWillNotDraw(false);
mTarget.setDrawingCacheEnabled(false);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BreathingLayout);
mAutoStart = a.getBoolean(R.styleable.BreathingLayout__autoStart, true);
mRippleColor = a.getColor(R.styleable.BreathingLayout__rippleColor, 0xFF0099CC);
mRippleRadius = a.getDimensionPixelSize(R.styleable.BreathingLayout__rippleStartRadius, -1);
if (mRippleRadius == -1) {
mRippleRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getContext().getResources().getDisplayMetrics());
}
mEndRadius = a.getDimensionPixelSize(R.styleable.BreathingLayout__rippleEndRadius, -1);
if (mEndRadius == -1) {
mEndRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getContext().getResources().getDisplayMetrics());
}
if (mEndRadius <= mRippleRadius) {
throw new IllegalStateException("rippleEndRadius can not be smaller than rippleStartRadius");
}
mDuration = a.getInt(R.styleable.BreathingLayout__rippleTime, 3000);
a.recycle();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(mRippleColor);
mPaint.setStyle(Paint.Style.FILL);
prepareAnimation();
if (mAutoStart) {
start();
}
}
Property mRadiusProperty = new Property(Float.class, "mRippleRadius") {
@Override
public Float get(BreathingDelegate object) {
return object.getRadius();
}
@Override
public void set(BreathingDelegate object, Float value) {
object.setRadius(value);
}
};
Property mAlphaProperty = new Property(Integer.class, "mRippleAlpha") {
@Override
public Integer get(BreathingDelegate object) {
return object.getRippleAlpha();
}
@Override
public void set(BreathingDelegate object, Integer value) {
object.setRippleAlpha(value);
}
};
private float getRadius() {
return mRippleRadius;
}
private void setRadius(float radius) {
this.mRippleRadius = radius;
}
public int getRippleAlpha() {
return mRippleAlpha;
}
public void setRippleAlpha(int rippleAlpha) {
this.mRippleAlpha = rippleAlpha;
}
private void prepareAnimation() {
ObjectAnimator animator = ObjectAnimator.ofFloat(this, mRadiusProperty, mRippleRadius, mEndRadius);
animator.setDuration(mDuration);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mTarget.invalidate();
}
});
ObjectAnimator alpha = ObjectAnimator.ofInt(this, mAlphaProperty, mRippleAlpha, 0x00);
alpha.setDuration(mDuration);
alpha.setRepeatCount(ValueAnimator.INFINITE);
alpha.setRepeatMode(ValueAnimator.RESTART);
mAnimatorSet.setInterpolator(new LinearInterpolator());
mAnimatorSet.playTogether(animator, alpha);
}
public void toggle() {
if (mAnimatorSet.isRunning()) {
stop();
}/* else if (mAnimatorSet.isPaused()) {
mAnimatorSet.resume();
} */ else {
mAnimatorSet.start();
}
}
public void start() {
mAnimatorSet.start();
}
public void stop() {
mAnimatorSet.cancel();
}
private Context getContext() {
return mTarget.getContext();
}
public void onDraw(Canvas canvas) {
mRippleRect.set(mBorderRect.centerX() - mRippleRadius, mBorderRect.centerY() - mRippleRadius,
mBorderRect.centerX() + mRippleRadius, mBorderRect.centerY() + mRippleRadius);
canvas.drawOval(mRippleRect, mPaint);
Log.d("BreathingLayout", "onDraw=" + mRippleRect.toString());
}
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
mBorderRect.set(left, top, right, bottom);
Log.d("BreathingLayout", "onLayout:" + mBorderRect.toString());
}
public void onDetachedFromWindow() {
mAnimatorSet.cancel();
Log.d("BreathingLayout", "onDetachedFromWindow");
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/BreathingLayout.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.FrameLayout;
/**
* Created by chaobin on 3/3/15.
*/
public class BreathingLayout extends FrameLayout {
BreathingDelegate mDelegate;
public BreathingLayout(Context context) {
this(context, null);
}
public BreathingLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BreathingLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDelegate = new BreathingDelegate(context, attrs, this);
}
@Override
protected void onDraw(Canvas canvas) {
mDelegate.onDraw(canvas);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mDelegate.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mDelegate.onDetachedFromWindow();
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/CurtainLayout.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
/**
* Created by aven on 12/25/15.
*/
public class CurtainLayout extends FrameLayout {
public CurtainLayout(Context context) {
this(context, null);
}
public CurtainLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/CustomSlidePanelLayout.java
================================================
package net.avenwu.support.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Build;
import android.support.v4.widget.SlidingPaneLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* Support level 11 and later;
* TODO use nineolddroid for devices under level 11
*
* Created by chaobin on 2/18/15.
*/
@TargetApi(11)
public class CustomSlidePanelLayout extends SlidingPaneLayout implements SlidingPaneLayout.PanelSlideListener {
protected View mMenuPanel;
protected float mSlideOffset;
protected boolean isCustomable = false;
public CustomSlidePanelLayout(Context context) {
this(context, null);
}
public CustomSlidePanelLayout(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public CustomSlidePanelLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (isInEditMode()) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
isCustomable = true;
super.setPanelSlideListener(this);
setSliderFadeColor(0);
}
}
@Override
public void onPanelSlide(View panel, float slideOffset) {
mSlideOffset = slideOffset;
if (mMenuPanel == null) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child != panel) {
mMenuPanel = child;
break;
}
}
}
final float scaleLeft = 1 - 0.3f * (1 - slideOffset);
mMenuPanel.setPivotX(-0.5f * mMenuPanel.getWidth());
mMenuPanel.setPivotY(mMenuPanel.getHeight() / 2f);
mMenuPanel.setScaleX(scaleLeft);
mMenuPanel.setScaleY(scaleLeft);
final float scale = 1 - 0.2f * slideOffset;
panel.setPivotX(0);
panel.setPivotY(panel.getHeight() / 2.0f);
panel.setScaleX(scale);
panel.setScaleY(scale);
if (mListener != null) {
mListener.onPanelSlide(panel, slideOffset);
}
}
@Override
public void onPanelOpened(View panel) {
if (mListener != null) {
mListener.onPanelOpened(panel);
}
}
@Override
public void onPanelClosed(View panel) {
if (mListener != null) {
mListener.onPanelClosed(panel);
}
}
PanelSlideListener mListener;
@Override
public void setPanelSlideListener(PanelSlideListener listener) {
if (isCustomable) {
mListener = listener;
} else {
super.setPanelSlideListener(listener);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isCustomable) {
dimOnForeground(canvas);
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
boolean result = super.drawChild(canvas, child, drawingTime);
if (isCustomable && child == mMenuPanel) {
dimOnForeground(canvas);
}
return result;
}
/**
* dim the view
*
* @param canvas
*/
private void dimOnForeground(Canvas canvas) {
canvas.drawColor(Color.argb((int) (0xff * (1 - mSlideOffset)), 0, 0, 0));
}
private boolean mSlideEnable = true;
public void setSlideEnable(boolean slideEnable) {
mSlideEnable = slideEnable;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mSlideEnable && super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return mSlideEnable && super.onTouchEvent(ev);
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/DimImageView.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ImageView;
import net.avenwu.support.R;
/**
* Created by chaobin on 1/7/15.
*/
public class DimImageView extends ImageView {
public static int DEFAULT_DIM = 0x99000000;
int mDimColor;
public DimImageView(Context context) {
this(context, null);
}
public DimImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DimImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DimImageView, defStyleAttr, 0);
mDimColor = array.getColor(R.styleable.DimImageView_dim, DEFAULT_DIM);
array.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(mDimColor);
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/DrawerFrame.java
================================================
package net.avenwu.support.widget;
import net.avenwu.support.BuildConfig;
import net.avenwu.support.R;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.Scroller;
public class DrawerFrame extends FrameLayout {
FrameLayout left;
FrameLayout main;
Scroller scroller;
public DrawerFrame(Context context) {
this(context, null);
}
public DrawerFrame(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DrawerFrame(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
// final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DrawerFrame, defStyle, 0);
// a.recycle();
scroller = new Scroller(getContext(), new Interpolator() {
@Override
public float getInterpolation(float input) {
return (float) (1.0D + Math.pow(input - 1.0D, 5.0D));
}
});
MENU_WIDTH = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
getResources().getDisplayMetrics());
main = new FrameLayout(getContext());
main.setId(R.id.main);
main.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
main.setBackgroundColor(getResources().getColor(android.R.color.holo_blue_bright));
addView(main);
left = new FrameLayout(getContext());
left.setId(R.id.menu);
left.setLayoutParams(new FrameLayout.LayoutParams(MENU_WIDTH, ViewGroup.LayoutParams.MATCH_PARENT));
addView(left);
ViewConfiguration configuration = ViewConfiguration.get(getContext());
touchSlop = configuration.getScaledTouchSlop();
}
float mSrcX;
int MENU_WIDTH;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!mSlidable) return false;
final int action = ev.getAction();
if (action != MotionEvent.ACTION_DOWN && isSliding) return true;
switch (action) {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
return false;
case MotionEvent.ACTION_MOVE:
if (Math.abs(ev.getX() - mSrcX) > touchSlop) {
mSrcX = ev.getX();
isSliding = true;
}
break;
case MotionEvent.ACTION_DOWN:
isSliding = false;
mSrcX = ev.getX();
break;
}
return isSliding;
}
boolean isSliding = false;
boolean mSlidable = true;
int touchSlop;
public void setSlide(boolean enable) {
mSlidable = enable;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
d("UIView", "event:" + event.toString());
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mSrcX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
int oldx = left.getScrollX();
float dx = mSrcX - event.getX();
if (dx != 0) {
left.setVisibility(VISIBLE);
float x = oldx + dx;
d("onTouchEvent", "move, oldx=" + oldx + ", dx=" + dx + ", mLeftView=" +
left.getLeft() + ", right=" + left.getRight() + ", getX=" + event.getX() + ", mSrcX=" + mSrcX);
d("onTouchEvent", "before x=" + x);
if (MENU_WIDTH < x) {
x = MENU_WIDTH;
}
if (0 > x) {
x = 0;
}
d("onTouchEvent", "after x=" + x);
left.scrollTo((int) x, 0);
mSrcX = event.getX();
}
break;
case MotionEvent.ACTION_UP:
int currentX = left.getScrollX();
if (currentX + mSrcX - event.getX() >= MENU_WIDTH / 2.0) {
int duration = (int) (Math.abs(MENU_WIDTH - currentX + 0.5f) / MENU_WIDTH * 1000);
scroller.startScroll(currentX, 0, MENU_WIDTH - currentX, 0, duration);
invalidate();
} else {
int duration = (int) (Math.abs(currentX + 0.5f) / MENU_WIDTH * 1000);
scroller.startScroll(currentX, 0, 0 - currentX, 0, duration);
invalidate();
}
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) continue;
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (child.getId() == R.id.menu) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
int childLeft = 0;
child.layout(childLeft, lp.topMargin, childLeft + childWidth, lp.topMargin + childHeight);
child.scrollTo(MENU_WIDTH, 0);
} else if (child.getId() == R.id.main) {
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
}
public void showMenuSmoothly() {
scroller.startScroll(MENU_WIDTH, 0, -MENU_WIDTH, 0, 1000);
invalidate();
}
public void dismissSmoothly() {
int start = scroller.getCurrX();
scroller.startScroll(start, 0, MENU_WIDTH - start, 0, 1000);
invalidate();
}
public void setMenuView(View view) {
left.removeAllViews();
left.addView(view);
requestLayout();
}
public void setContentView(View view) {
main.removeAllViews();
main.addView(view);
requestLayout();
}
@Override
public void computeScroll() {
d("computeScroll", "computeScroll");
if (scroller.computeScrollOffset()) {
int oldx = left.getScrollX();
int x = scroller.getCurrX();
d("computeScroll", "try scroll, oldx=" + oldx + ", x=" + x);
if (oldx != x) {
//this can only effect on the content view inside of mLeftView
left.scrollTo(x, 0);
left.invalidate();
}
invalidate();
} else {
scroller.abortAnimation();
}
}
private void d(String tag, String text) {
if (BuildConfig.DEBUG) {
Log.d(tag, text);
}
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/DrawerFrameV2.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import android.widget.Scroller;
import net.avenwu.support.R;
public class DrawerFrameV2 extends FrameLayout {
View mLeftView;
View mContentView;
Scroller mScroller;
float mSrcX;
int mLeftViewWidth;
boolean isSliding = false;
boolean mSlidable = true;
int mTouchSlop;
public DrawerFrameV2(Context context) {
this(context, null);
}
public DrawerFrameV2(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DrawerFrameV2(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
mScroller = new Scroller(getContext(), new Interpolator() {
@Override
public float getInterpolation(float input) {
return (float) (1.0D + Math.pow(input - 1.0D, 5.0D));
}
});
mLeftViewWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
getResources().getDisplayMetrics());
ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mShadowLeft = getResources().getDrawable(android.R.drawable.ic_media_play);
}
@Override
protected void onFinishInflate() {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getId() == R.id.menu) {
mLeftView = child;
} else if (child.getId() == R.id.main) {
mContentView = child;
}
}
if (mLeftView == null) {
mLeftView = new FrameLayout(getContext());
mLeftView.setId(R.id.menu);
mLeftView.setLayoutParams(new LayoutParams(mLeftViewWidth, ViewGroup.LayoutParams.MATCH_PARENT));
mLeftView.setBackgroundColor(0xff669900);
addView(mLeftView);
}
if (mContentView == null) {
mContentView = new FrameLayout(getContext());
mContentView.setId(R.id.main);
mContentView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mContentView.setBackgroundColor(0xffff8800);
addView(mContentView);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!mSlidable) return false;
final int action = ev.getAction();
if (action != MotionEvent.ACTION_DOWN && isSliding) return true;
switch (action) {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
return false;
case MotionEvent.ACTION_MOVE:
if (Math.abs(ev.getX() - mSrcX) > mTouchSlop) {
mSrcX = ev.getX();
isSliding = true;
}
break;
case MotionEvent.ACTION_DOWN:
isSliding = false;
mSrcX = ev.getX();
break;
}
return isSliding;
}
public void setSlide(boolean enable) {
mSlidable = enable;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
d("UIView", "event:" + event.toString());
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mSrcX = event.getX();
// TODO after left menu drag outside screen, the menu is somewhat invisible while drag in again?
mLeftView.setVisibility(VISIBLE);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
float dx = event.getX() - mSrcX;
int x = mLeftView.getLeft();
if (dx != 0) {
if (dx + x > 0) {
dx = 0 - x;
}
mLeftView.offsetLeftAndRight((int) dx);
mSrcX = event.getX();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
int currentX = mLeftView.getLeft();
if (currentX + event.getX() - mSrcX >= -mLeftViewWidth / 2.0) {
int duration = (int) (Math.abs(0 - currentX + 0.5f) / mLeftViewWidth * 1000);
mScroller.startScroll(currentX, 0, 0 - currentX, 0, duration);
} else {
int duration = (int) (Math.abs(-mLeftViewWidth - currentX + 0.5f) / mLeftViewWidth * 1000);
mScroller.startScroll(currentX, 0, -mLeftViewWidth - currentX, 0, duration);
}
invalidate();
break;
}
return true;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) continue;
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (child.getId() == R.id.menu) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
int childLeft = 0;
child.layout(childLeft, lp.topMargin, childLeft + childWidth, lp.topMargin + childHeight);
child.offsetLeftAndRight(-mLeftViewWidth);
} else if (child.getId() == R.id.main) {
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
}
Drawable mShadowLeft;// = new ColorDrawable(Color.RED);
private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
private int mScrimColor = DEFAULT_SCRIM_COLOR;
private float mScrimOpacity;
private Paint mScrimPaint = new Paint();
float alpha;
/* @Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
boolean result = super.drawChild(canvas, child, drawingTime);
if (child.getId() == R.id.main) {
int clipLeft = 0, clipRight = getWidth();
final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
// final int imag = (int) (baseAlpha * mScrimOpacity);
// final int color = imag << 24 | (mScrimColor & 0xffffff);
mScrimPaint.setColor(Color.argb((int) (255 * alpha * 0xff), 0, 0, 0));
canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint);
} else if (child.getId() == R.id.menu) {
final int shadowWidth = mShadowLeft.getIntrinsicWidth();
final int childRight = child.getRight();
final int drawerPeekDistance = mLeftViewWidth;
alpha =
Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f));
mShadowLeft.setBounds(childRight, child.getTop(),
childRight + shadowWidth, child.getBottom());
mShadowLeft.setAlpha((int) (0xff * alpha));
mShadowLeft.draw(canvas);
}
return result;
}*/
boolean isContentView(View child) {
return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
}
boolean isDrawerView(View child) {
final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
// final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
// ViewCompat.getLayoutDirection(child));
// return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
return gravity == Gravity.LEFT || gravity == Gravity.RIGHT;
}
public void showMenuSmoothly() {
mScroller.startScroll(mLeftViewWidth, 0, -mLeftViewWidth, 0, 1000);
invalidate();
}
public void dismissSmoothly() {
int start = mScroller.getCurrX();
mScroller.startScroll(start, 0, mLeftViewWidth - start, 0, 1000);
invalidate();
}
public void setMenuView(View view) {
if (mLeftView instanceof ViewGroup) {
((ViewGroup) mLeftView).removeAllViews();
((ViewGroup) mLeftView).addView(view);
requestLayout();
}
}
public void setContentView(View view) {
if (mContentView instanceof ViewGroup) {
((ViewGroup) mContentView).removeAllViews();
((ViewGroup) mContentView).addView(view);
requestLayout();
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
int oldLeft = mLeftView.getLeft();
int left = mScroller.getCurrX();
if (oldLeft != mScroller.getFinalX()) {
int xOffset = left - oldLeft;
int dx = xOffset + oldLeft;
if (dx > 0) {
xOffset = 0 - oldLeft;
} else if (dx < -mLeftViewWidth) {
xOffset = -mLeftViewWidth - oldLeft;
}
mLeftView.offsetLeftAndRight(xOffset);
onScrollChanged(left, 0, oldLeft, 0);
}
invalidate();
} else {
mScroller.abortAnimation();
}
}
private void d(String tag, String text) {
// if (BuildConfig.DEBUG) {
Log.d(tag, text);
// }
}
/**
* This is called in response to an internal scroll in this view (i.e., the
* view scrolled its own contents). This is typically as a result of
* {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
* called.
*
* @param l Current horizontal scroll origin.
* @param t Current vertical scroll origin.
* @param oldl Previous horizontal scroll origin.
* @param oldt Previous vertical scroll origin.
*/
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/ExProgressView.java
================================================
package net.avenwu.support.widget;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
/**
* Created by aven on 10/20/15.
*/
@TargetApi(14)
public class ExProgressView extends View {
Paint mPaint;
Rect mRect;
int mRectWidth;
int mRectHeight;
int mGap;
int[] mIndexArray = {-2, -1, 0, 1, 2};
int mCurrentIndex = 4;
public ExProgressView(Context context) {
this(context, null);
}
public ExProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.GREEN);
mRect = new Rect();
mRectWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20,
getContext().getResources().getDisplayMetrics());
mRectHeight = 4 * mRectWidth;
mGap = (int) (mRectWidth * 0.8f);
ObjectAnimator animator = ObjectAnimator.ofInt(this, mIndex, 0, 1, 2, 3, 4, 5);
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(750);
animator.setRepeatMode(Animation.RESTART);
animator.setRepeatCount(Animation.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
animator.start();
}
public int getmCurrentIndex() {
return mCurrentIndex;
}
public void setmCurrentIndex(int mCurrentIndex) {
this.mCurrentIndex = mCurrentIndex;
}
Property mIndex = new Property(Integer.class, "mCurrentIndex") {
@Override
public Integer get(ExProgressView object) {
return object.getmCurrentIndex();
}
@Override
public void set(ExProgressView object, Integer value) {
object.setmCurrentIndex(value);
}
};
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mRect.width() > 0 && mRect.height() > 0) {
for (int index : mIndexArray) {
float[] t = getRectByIndex(mRect, index);
if (index == mIndexArray[mCurrentIndex]) {
t[1] = mRect.top;
t[3] = mRect.bottom;
Log.d("Index", "value=" + index);
}
canvas.drawRect(t[0], t[1], t[2], t[3], mPaint);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Rect rect = mRect;
final int centerX = getMeasuredWidth() / 2;
final int centerY = getMeasuredHeight() / 2;
rect.left = centerX - mRectWidth / 2;
rect.right = centerX + mRectWidth / 2;
rect.top = centerY - mRectHeight / 2;
rect.bottom = centerY + mRectHeight / 2;
}
private float[] getRectByIndex(Rect rect, int index) {
final float diffX = index * (mRectWidth + mGap);
final float diffY = index * (mRectHeight * 0.15f);
return new float[]{
rect.left + diffX,
rect.top + Math.abs(diffY),
rect.right + diffX,
rect.bottom - Math.abs(diffY)
};
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/ExTextView.java
================================================
package net.avenwu.support.widget;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.TextView;
import net.avenwu.support.R;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
/**
* Created by chaobin on 11/18/15.
*/
@TargetApi(11)
public class ExTextView extends TextView implements View.OnClickListener, ValueAnimator.AnimatorUpdateListener {
static final String TAG = ExTextView.class.getCanonicalName();
static final int END = 0;
static final int RIGHT = 1;
int mMaxHeight;
int mCollapsedHeight;
int mMaxLine;
boolean isCollapsed;
boolean isLayout = false;
boolean isMeasured = false;
ValueAnimator mExpandAnimator;
CharSequence mCollapsedText;
CharSequence mFullText;
Drawable mIndicator;
OnClickListener mOuterListener;
Style mStyle;
public ExTextView(Context context) {
this(context, null);
}
public ExTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ExTextView);
isCollapsed = a.getBoolean(R.styleable.ExTextView_expand_collapse_default, true);
mIndicator = a.getDrawable(R.styleable.ExTextView_expand_indicator);
if (mIndicator != null) {
mIndicator.setBounds(0, 0, mIndicator.getIntrinsicWidth(), mIndicator.getIntrinsicHeight
());
}
int style = a.getInt(R.styleable.ExTextView_expand_style, 0);
switch (style) {
case END:
mStyle = new EndStyle();
break;
case RIGHT:
mStyle = new RightStyle();
break;
}
a.recycle();
reflectMaxLines();
super.setOnClickListener(this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d(TAG, "onMeasure");
if (!TextUtils.isEmpty(mFullText) && !isMeasured) {
Log.d(TAG, "onMeasure isCollapsed=" + isCollapsed);
if (isCollapsed) {
setMaxLines(Integer.MAX_VALUE);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mMaxHeight = getMeasuredHeight();
setMaxLines(mMaxLine);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mCollapsedHeight = getMeasuredHeight();
} else {
setMaxLines(mMaxLine);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mCollapsedHeight = getMeasuredHeight();
setMaxLines(Integer.MAX_VALUE);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mMaxHeight = getMeasuredHeight();
}
isMeasured = true;
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (!TextUtils.isEmpty(mFullText) && (!isLayout && isExpandable())) {
Log.d(TAG, "onLayout reset text");
mStyle.onLayout(isCollapsed, this, mIndicator);
if (isCollapsed) {
if (mCollapsedText == null) {
mCollapsedText = mStyle.collapsedText(this, mIndicator, mFullText, mMaxLine);
}
super.setText(mCollapsedText, reflectCurrentBufferType());
} else {
super.setText(mFullText, reflectCurrentBufferType());
}
isLayout = true;
} else {
super.onLayout(changed, left, top, right, bottom);
}
}
private void reflectMaxLines() {
try {
Field maximumField = TextView.class.getDeclaredField("mMaximum");
maximumField.setAccessible(true);
mMaxLine = maximumField.getInt(this);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private BufferType reflectCurrentBufferType() {
try {
Field field = TextView.class.getDeclaredField("mBufferType");
field.setAccessible(true);
return (BufferType) field.get(this);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return BufferType.NORMAL;
}
@Override
public void setText(CharSequence text, BufferType type) {
//view 复用的时候会重复绑定数据
if (TextUtils.isEmpty(text)) {
super.setText(text, type);
} else if (text.equals(mFullText)) {
super.setText(isCollapsed ? mCollapsedText : mFullText, reflectCurrentBufferType());
} else {
mFullText = text;
mCollapsedText = null;
isLayout = false;
isMeasured = false;
super.setText(mFullText, type);
}
}
@Override
public void onClick(View v) {
if (mOuterListener != null) {
mOuterListener.onClick(v);
}
if (!isExpandable()) {
return;
}
if (mExpandAnimator != null) {
mExpandAnimator.end();
}
isLayout = false;
int from = getHeight();
int to;
if (from < mMaxHeight) {
to = mMaxHeight;
isCollapsed = false;
setMaxLines(Integer.MAX_VALUE);
} else {
to = mCollapsedHeight;
isCollapsed = true;
setMaxLines(mMaxLine);
}
if (mExpandAnimator == null) {
mExpandAnimator = ValueAnimator.ofInt(from, to);
mExpandAnimator.setInterpolator(new DecelerateInterpolator());
mExpandAnimator.addUpdateListener(this);
} else {
mExpandAnimator.setIntValues(from, to);
}
mExpandAnimator.start();
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
getLayoutParams().height = (Integer) animation.getAnimatedValue();
requestLayout();
}
private boolean isExpandable() {
return mCollapsedHeight != mMaxHeight;
}
@Override
public void setOnClickListener(OnClickListener listener) {
mOuterListener = listener;
}
public interface Style {
CharSequence collapsedText(TextView textView, Drawable drawable, CharSequence text, int
maxLine);
void onLayout(boolean collapsed, TextView textView, Drawable drawable);
}
private static class EndStyle implements Style {
static final String HTML_IMG = "...
";
static final String HTML_NEW_LINE = "
";
@Override
public CharSequence collapsedText(TextView view, final Drawable drawable, CharSequence text,
int maxLine) {
StringBuilder stringBuilder = new StringBuilder();
int start = 0;
// 由于中英文字符等排版问题断行具有不确定性,此处强行对缩略文本断行
for (int i = 0; i < maxLine; i++) {
int end = view.getLayout().getLineVisibleEnd(i);
String append;
if (i == maxLine - 1) {
end -= 3;
append = HTML_IMG;
} else {
append = HTML_NEW_LINE;
}
stringBuilder.append(text.subSequence(start, end)).append(append);
start = end;
}
String subString = stringBuilder.toString();
return Html.fromHtml(subString, new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
return "icon".equals(source) ? drawable : null;
}
}, null);
}
@Override
public void onLayout(boolean collapsed, TextView textView, Drawable drawable) {
}
}
private static class RightStyle implements Style {
@Override
public CharSequence collapsedText(TextView view, Drawable drawable, CharSequence text,
int maxLine) {
int end = view.getLayout().getLineVisibleEnd(maxLine - 1);
return text.subSequence(0, end - 3) + "...";
}
@Override
public void onLayout(boolean collapsed, TextView textView, Drawable drawable) {
Drawable[] d = textView.getCompoundDrawables();
if (collapsed) {
d[2] = drawable;
} else {
d[2] = null;
}
textView.setCompoundDrawables(d[0], d[1], d[2], d[3]);
}
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/FlatTabGroup.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import net.avenwu.support.R;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by chaobin on 2/4/15.
*/
public class FlatTabGroup extends RadioGroup implements RadioGroup.OnCheckedChangeListener {
public FlatTabGroup(Context context) {
this(context, null);
}
private int mRadius;
private int mStroke;
private int mHighlightColor;
private String[] mItemString;
private float mTextSize;
private ColorStateList mTextColor;
private int[] mTabViewIds;
private OnTabCheckedListener mTabCheckedListener;
private OnCheckedChangeListener mListener;
public FlatTabGroup(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FlatTabGroup);
mHighlightColor = array.getColor(R.styleable.FlatTabGroup_tab_border_color, Color.WHITE);
mStroke = array.getDimensionPixelSize(R.styleable.FlatTabGroup_tab_border_width, 2);
mRadius = array.getDimensionPixelOffset(R.styleable.FlatTabGroup_tab_radius, 5);
mTextColor = array.getColorStateList(R.styleable.FlatTabGroup_tab_textColor);
mTextSize = array.getDimensionPixelSize(R.styleable.FlatTabGroup_tab_textSize, 14);
int id = array.getResourceId(R.styleable.FlatTabGroup_tab_items, 0);
mItemString = isInEditMode() ? new String[]{"TAB A", "TAB B", "TAB C"} : context.getResources().getStringArray(id);
generateTabView(context, attrs);
updateChildBackground();
array.recycle();
super.setOnCheckedChangeListener(this);
}
private void generateTabView(Context context, AttributeSet attrs) {
if (mItemString == null) {
return;
}
mTabViewIds = new int[mItemString.length];
int i = 0;
for (String text : mItemString) {
RadioButton button = new RadioButton(context, attrs);
button.setGravity(Gravity.CENTER);
button.setButtonDrawable(android.R.color.transparent);
button.setText(text);
button.setTextColor(mTextColor);
button.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
int id = generateViewId();
button.setId(mTabViewIds[i++] = id);
addView(button, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1));
}
}
public void setOnTabCheckedListener(OnTabCheckedListener listener) {
mTabCheckedListener = listener;
}
public void setSelection(int position) {
check(getChildAt(position).getId());
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (mListener != null) {
mListener.onCheckedChanged(group, checkedId);
}
if (mTabCheckedListener != null) {
int checkedPosition = -1;
for (int i = 0; i < mTabViewIds.length; i++) {
if (mTabViewIds[i] == checkedId) {
checkedPosition = i;
break;
}
}
mTabCheckedListener.onChecked(this, checkedPosition);
}
}
/**
* use {@linkplain #setOnTabCheckedListener(net.avenwu.support.widget.FlatTabGroup.OnTabCheckedListener)} instead
*/
@Deprecated()
@Override
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mListener = listener;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
updateChildBackground();
}
private void updateChildBackground() {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child instanceof RadioButton) {
child.setBackgroundDrawable(generateTabBackground(i, mHighlightColor));
}
}
}
private Drawable generateTabBackground(int position, int color) {
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(new int[]{android.R.attr.state_checked}, generateDrawable(position, color));
stateListDrawable.addState(new int[]{}, generateDrawable(position, Color.TRANSPARENT));
return stateListDrawable;
}
private Drawable generateDrawable(int position, int color) {
float[] radius;
if (position == 0) {
radius = new float[]{
mRadius, mRadius,
0, 0,
0, 0,
mRadius, mRadius
};
} else if (position == getChildCount() - 1) {
radius = new float[]{
0, 0,
mRadius, mRadius,
mRadius, mRadius,
0, 0
};
} else {
radius = new float[]{
0, 0,
0, 0,
0, 0,
0, 0
};
}
GradientDrawable shape = new GradientDrawable();
shape.setCornerRadii(radius);
shape.setColor(color);
shape.setStroke(mStroke, mHighlightColor);
return shape;
}
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
/**
* Generate a value suitable for use in {@link #setId(int)}.
* This value will not collide with ID values generated at build time by aapt for R.id.
*
* @return a generated ID value
*/
public static int generateViewId() {
for (; ; ) {
final int result = sNextGeneratedId.get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result;
}
}
}
public static interface OnTabCheckedListener {
public void onChecked(FlatTabGroup group, int position);
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/FlipLayout.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.FrameLayout;
/**
* Created by aven on 1/19/16.
*/
public class FlipLayout extends FrameLayout {
View mFrontView;
View mBackView;
boolean isFlipping = false;
public FlipLayout(Context context) {
this(context, null);
}
public FlipLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() > 1) {
throw new IllegalArgumentException("FlipLayout can only have to child view");
}
super.addView(child, index, params);
if (mBackView == null) {
mBackView = child;
} else {
mFrontView = child;
}
}
public void setViewOrder(View front, View back) {
mFrontView = front;
mBackView = back;
mFrontView.setVisibility(VISIBLE);
mBackView.setVisibility(INVISIBLE);
}
/**
* toggle the flip state
*/
public void flip() {
if (isFlipping) {
return;
}
mBackView.setVisibility(GONE);
mFrontView.setVisibility(VISIBLE);
final float centerX = mFrontView.getWidth() / 2f;
final float centerY = mFrontView.getHeight() / 2f;
final FlipAnimation frontViewAnimation = new FlipAnimation(centerX, centerY, true);
frontViewAnimation.setDuration(500);
frontViewAnimation.setFillAfter(true);
frontViewAnimation.setInterpolator(new AccelerateInterpolator());
frontViewAnimation.setAnimationListener(new SimpleAnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
mBackView.setVisibility(VISIBLE);
mFrontView.setVisibility(INVISIBLE);
final float centerX = mBackView.getWidth() / 2f;
final float centerY = mBackView.getHeight() / 2f;
FlipAnimation backViewAnimation = new FlipAnimation(centerX, centerY, false);
backViewAnimation.setDuration(500);
backViewAnimation.setFillAfter(true);
backViewAnimation.setInterpolator(new DecelerateInterpolator());
backViewAnimation.setAnimationListener(new SimpleAnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
View temp = mFrontView;
mFrontView = mBackView;
mBackView = temp;
isFlipping = false;
}
});
mBackView.startAnimation(backViewAnimation);
}
});
mFrontView.startAnimation(frontViewAnimation);
isFlipping = true;
}
private static class FlipAnimation extends Animation {
private final float mFrom;
private final float mTo;
private final float mCenterX;
private final float mCenterY;
private final float SCALE_DEFAULT = 0.618f;
private final float ALPHA_DEFAULT = 0.8f;
private Camera mCamera;
private boolean mScaleDown;
public FlipAnimation(float mCenterX, float mCenterY, boolean scaleDown) {
this.mCenterX = mCenterX;
this.mCenterY = mCenterY;
mScaleDown = scaleDown;
if (scaleDown) {
this.mFrom = 0;
this.mTo = 90;
} else {
this.mFrom = -90;
this.mTo = 0;
}
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float degree = mFrom + (mTo - mFrom) * interpolatedTime;
float scale = mScaleDown ? (1 - (1 - SCALE_DEFAULT) * interpolatedTime) :
(SCALE_DEFAULT + (1 - SCALE_DEFAULT) * interpolatedTime);
float alpha = mScaleDown ? (1 - (1 - ALPHA_DEFAULT) * interpolatedTime) :
(ALPHA_DEFAULT + (1 - ALPHA_DEFAULT) * interpolatedTime);
t.setAlpha(alpha);
final Matrix matrix = t.getMatrix();
mCamera.save();
mCamera.rotateY(degree);
mCamera.getMatrix(matrix);
mCamera.restore();
matrix.preTranslate(-mCenterX, -mCenterY);
matrix.postTranslate(mCenterX, mCenterY);
matrix.preScale(scale, scale, mCenterX, mCenterY);
}
}
private static class SimpleAnimationListener implements Animation.AnimationListener {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/MatrixFrameLayout.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.util.AttributeSet;
import android.view.animation.Transformation;
import android.widget.FrameLayout;
/**
* Created by aven on 1/20/16.
*/
public class MatrixFrameLayout extends FrameLayout {
Camera mCamera = new Camera();
Transformation mTransformation = new Transformation();
private PaintFlagsDrawFilter mPaintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
public MatrixFrameLayout(Context context) {
this(context, null);
}
public MatrixFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
}
@Override
public void draw(Canvas canvas) {
mTransformation.clear();
mTransformation.setTransformationType(Transformation.TYPE_MATRIX);
Matrix matrix = mTransformation.getMatrix();
float degree = 15;
mCamera.save();
mCamera.rotateY(degree);
mCamera.getMatrix(matrix);
mCamera.restore();
final float centerY = getHeight() / 2f;
matrix.preTranslate(0, -centerY);
matrix.postTranslate(0, centerY);
canvas.save();
canvas.concat(mTransformation.getMatrix());
canvas.setDrawFilter(mPaintFlagsDrawFilter);
super.draw(canvas);
canvas.restore();
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/PolygonWithQuadraticBezirView.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
/**
* Created by chaobin on 12/25/14.
*/
public class PolygonWithQuadraticBezirView extends View {
Paint mCirclePaint;
float mPointOneX;
float mPointOneY;
float mPointTwoX;
float mPointTwoY;
float mPointOneRadius;
float mPointTwoRadius;
Path mPath;
Paint mShape;
float MAX_HORIZOTNAL_DISTANCE;
float MAX_VERTICAL_DISTANCE;
public PolygonWithQuadraticBezirView(Context context) {
this(context, null);
}
public PolygonWithQuadraticBezirView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PolygonWithQuadraticBezirView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setColor(0xff00bcd4);
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
MAX_HORIZOTNAL_DISTANCE = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 240, metrics);
MAX_VERTICAL_DISTANCE = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, metrics);
mPointOneX = MAX_VERTICAL_DISTANCE * 0.4f;
mPointOneY = mPointOneX;
mPointTwoX = mPointOneX * 1.75f;
mPointTwoY = MAX_HORIZOTNAL_DISTANCE * 0.5f;
mPointOneRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, metrics);
mPointTwoRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, metrics);
mPath = new Path();
mShape = new Paint(Paint.ANTI_ALIAS_FLAG);
mShape.setColor(Color.RED);
mShape.setStyle(Paint.Style.STROKE);
mShape.setStrokeWidth(1);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.TRANSPARENT);
canvas.drawCircle(mPointOneX, mPointOneY, mPointOneRadius, mCirclePaint);
canvas.drawCircle(mPointTwoX, mPointTwoY, mPointTwoRadius, mCirclePaint);
mPath.reset();
float d = (float) Math.sqrt(Math.pow(mPointTwoX - mPointOneX, 2) + Math.pow(mPointTwoY - mPointOneY, 2));
float midX = (mPointOneX + mPointTwoX) / 2.0f;
float midY = (mPointOneY + mPointTwoY) / 2.0f;
float k = (mPointTwoY - mPointOneY) / (mPointTwoX - mPointOneX);
float offsetX = (float) (mPointOneRadius * Math.sin(Math.atan(k)));
float offsetY = (float) (mPointOneRadius * Math.cos(Math.atan(k)));
float offsetX2 = (float) (mPointTwoRadius * Math.sin(Math.atan(k)));
float offsetY2 = (float) (mPointTwoRadius * Math.cos(Math.atan(k)));
float x1 = mPointOneX - offsetX;
float y1 = mPointOneY + offsetY;
float x2 = mPointTwoX - offsetX2;
float y2 = mPointTwoY + offsetY2;
float x3 = mPointTwoX + offsetX2;
float y3 = mPointTwoY - offsetY2;
float x4 = mPointOneX + offsetX;
float y4 = mPointOneY - offsetY;
mPath.moveTo(x1, y1);
mPath.quadTo(midX, midY, x2, y2);
mPath.lineTo(x3, y3);
mPath.quadTo(midX, midY, x4, y4);
mPath.lineTo(x1, y1);
canvas.drawPath(mPath, mShape);
}
public void setFilled(boolean fill) {
if (fill) {
mShape.setStyle(Paint.Style.FILL);
mShape.setColor(mCirclePaint.getColor());
} else {
mShape.setStyle(Paint.Style.STROKE);
mShape.setColor(Color.RED);
}
invalidate();
}
public void moveHorizontal(float percent) {
mPointTwoX = MAX_HORIZOTNAL_DISTANCE * percent;
invalidate();
}
public void moveVertical(float percent) {
mPointTwoY = MAX_VERTICAL_DISTANCE * percent;
invalidate();
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/RefreshLayout.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.BounceInterpolator;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Scroller;
import android.widget.TextView;
/**
* Created by Chaobin Wu on 2014/10/10.
*/
public class RefreshLayout extends LinearLayout {
private FrameLayout mHeaderLayout;
private ListView mListView;
private float mX;
private float mY;
private Scroller mScroller;
private int mPaddingTop = 0;
private int MAX_OVER_SCROLL_HEIGHT;
boolean isSliding = false;
int touchSlop;
public RefreshLayout(Context context) {
this(context, null);
}
public RefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
ViewConfiguration configuration = ViewConfiguration.get(getContext());
touchSlop = configuration.getScaledTouchSlop();
mScroller = new Scroller(context, new BounceInterpolator());
mHeaderLayout = new FrameLayout(context);
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
mHeaderLayout.setLayoutParams(layoutParams);
mHeaderLayout.setBackgroundColor(getResources().getColor(android.R.color.holo_orange_dark));
addView(mHeaderLayout);
//TODO add text header
TextView textView = new TextView(context);
textView.setText("This is header layout content");
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, getResources().getDisplayMetrics());
LayoutParams textParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, height);
textParams.gravity = Gravity.CENTER;
textView.setLayoutParams(textParams);
addHeaderChild(textView);
mPaddingTop -= height;
setPadding(0, mPaddingTop, 0, 0);
MAX_OVER_SCROLL_HEIGHT = 2 * height;
mListView = new ListView(context, attrs);
mListView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
addView(mListView);
}
public void addHeaderChild(View view) {
if (mHeaderLayout != null) {
mHeaderLayout.addView(view);
requestLayout();
}
}
public void setAdapter(ListAdapter adapter) {
mListView.setAdapter(adapter);
if (adapter instanceof BaseAdapter) {
((BaseAdapter) adapter).notifyDataSetChanged();
}
}
private boolean isFirstItemVisible() {
if (null == mListView.getAdapter() || mListView.getAdapter().isEmpty()) {
return true;
} else {
return mListView.getFirstVisiblePosition() == 0;
// if (mListView.getFirstVisiblePosition() <= 1) {
// final View firstVisibleChild = mListView.getChildAt(0);
// if (firstVisibleChild != null) {
// return firstVisibleChild.getTop() >= mListView.getTop();
// }
// }
}
// return false;
}
private boolean isLastItemVisible() {
if (null == mListView.getAdapter() || mListView.getAdapter().isEmpty()) {
return true;
} else {
return mListView.getLastVisiblePosition() == mListView.getAdapter().getCount() - 1;
}
}
public ListView getListView() {
return mListView;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
if (action != MotionEvent.ACTION_DOWN && isSliding) return true;
switch (action) {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
isSliding = false;
break;
case MotionEvent.ACTION_MOVE:
Log.d("onInterceptTouchEvent", "scrollY=" + getScrollY() + ", " + ev.toString());
if (ev.getY() > mY) {
if (isFirstItemVisible() && Math.abs(ev.getY() - mY) > touchSlop) {
mY = ev.getY();
isSliding = true;
}
} else {
if (getScrollY() < 0) {
mY = ev.getY();
isSliding = true;
}
}
break;
case MotionEvent.ACTION_DOWN:
isSliding = false;
mY = ev.getY();
break;
}
return isSliding || super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("RefreshLayout", event.toString());
boolean eatTouchEvent = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mY = event.getY();
eatTouchEvent = true;
break;
case MotionEvent.ACTION_MOVE:
if (isSliding) {// move down:getScrollY becomes smaller
float y = event.getY();
float diff = y - mY;
mY = event.getY();
if (diff != 0) {
float distance = getScrollY() - diff;
Log.d("RefreshLayout", "move, distance=" + distance + ", getScrollY=" + getScrollY() + ", diff=" + diff);
if (distance > 0) {
distance = Math.min(distance, MAX_OVER_SCROLL_HEIGHT);
} else {
distance = Math.max(distance, -MAX_OVER_SCROLL_HEIGHT);
}
scrollTo(getScrollX(), (int) distance);
}
eatTouchEvent = true;
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (isSliding) {
isSliding = false;
mScroller.abortAnimation();
int currentY = getScrollY();
int desY = -currentY - MAX_OVER_SCROLL_HEIGHT / 2;
Log.d("RefreshLayout", "getScrollY=" + currentY + ", desY=" + desY);
//current y > 2/3 of max height, scroll to display the whole header
if (currentY + event.getY() - mY < -MAX_OVER_SCROLL_HEIGHT / 2) {
mScroller.startScroll(0, currentY, 0, desY);
} else {
mScroller.startScroll(0, currentY, 0, 0 - currentY);
}
invalidate();
eatTouchEvent = true;
}
break;
}
return eatTouchEvent || super.onTouchEvent(event);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
int oldY = getScrollY();
int y = mScroller.getCurrY();
if (oldY != y) {
scrollTo(0, y);
}
invalidate();
} else {
mScroller.abortAnimation();
}
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/SegmentDrawable.java
================================================
package net.avenwu.support.widget;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
/**
* Segment tab background drawable which can not realized by xml shape drawable
*
* @author aven
*/
public class SegmentDrawable extends Drawable {
Paint mPaint;
Path mPath;
int mStyle;
int mCornerRadius = 10;
public interface Style {
int LEFT_EDGE = 0;
int MIDDLE = 1;
int RIGHT_EDGE = 2;
}
public SegmentDrawable(int style) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setDither(true);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPath = new Path();
mStyle = style;
}
public SegmentDrawable(SegmentDrawable drawable) {
mPaint = new Paint(drawable.mPaint);
mPath = new Path(drawable.mPath);
mStyle = drawable.mStyle;
mCornerRadius = drawable.mCornerRadius;
}
public void setStrokeWidth(int px) {
mPaint.setStrokeWidth(px);
}
public void setCornerRadius(int radius) {
mCornerRadius = radius;
}
public void setColor(int color) {
mPaint.setColor(color);
}
@Override
public void draw(Canvas canvas) {
canvas.drawPath(mPath, mPaint);
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
updateShape();
}
private void updateShape() {
Path path = mPath;
if (path != null) {
path.reset();
int width = 2 * mCornerRadius;
final Rect r = getBounds();
final float strokeWidth = mPaint.getStrokeWidth();
switch (mStyle) {
case Style.LEFT_EDGE:
RectF topLeftRect = new RectF(strokeWidth, strokeWidth, width, width);
path.arcTo(topLeftRect, 180, 90);
path.lineTo(r.width(), strokeWidth);
path.lineTo(r.width(), r.height() - strokeWidth);
path.lineTo(width + strokeWidth, r.height() - strokeWidth);
RectF BottomLeftRect = new RectF(strokeWidth, r.height() - width - strokeWidth, width, r.height() - strokeWidth);
path.arcTo(BottomLeftRect, 90, 90);
path.close();
break;
case Style.MIDDLE:
path.moveTo(0, strokeWidth);
path.lineTo(r.width(), strokeWidth);
path.lineTo(r.width(), r.height() - strokeWidth);
path.lineTo(0, r.height() - strokeWidth);
path.close();
break;
case Style.RIGHT_EDGE:
path.moveTo(0, strokeWidth);
path.lineTo(r.width() - width - strokeWidth, strokeWidth);
RectF topRightRect = new RectF(r.width() - width - strokeWidth, strokeWidth, r.width() - strokeWidth, width + strokeWidth);
path.arcTo(topRightRect, 270, 90);
path.lineTo(r.width() - strokeWidth, r.height() - width - strokeWidth);
RectF bottomRightRect = new RectF(r.width() - width - strokeWidth, r.height() - width - strokeWidth, r.width() - strokeWidth, r.height() - strokeWidth);
path.arcTo(bottomRightRect, 0, 90);
path.lineTo(0, r.height() - strokeWidth);
path.close();
break;
}
}
}
@Override
protected SegmentDrawable clone() throws CloneNotSupportedException {
return new SegmentDrawable(this);
}
public StateListDrawable newStateListDrawable() {
StateListDrawable stateListDrawable = new StateListDrawable();
try {
SegmentDrawable checked = clone();
checked.mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
stateListDrawable.addState(new int[]{android.R.attr.state_checked}, checked);
stateListDrawable.addState(new int[]{android.R.attr.state_selected}, checked);
stateListDrawable.addState(new int[]{}, this);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stateListDrawable;
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/ShaderImageView.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
/**
* Reference: http://chiuki.github.io/android-shaders-filters/#/40
* Created by chaobin on 2/3/15.
*/
public class ShaderImageView extends ImageView {
Paint paint;
boolean shouldDrawOpening = true;
PointF mCenterPoint = new PointF();
public ShaderImageView(Context context) {
this(context, null);
}
public ShaderImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ShaderImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
if (paint == null) {
Bitmap original = Bitmap.createBitmap(
getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas originalCanvas = new Canvas(original);
super.onDraw(originalCanvas);
Shader shader = new BitmapShader(original,
Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setShader(shader);
greyBitmap = drawBitmap(original);
}
canvas.drawBitmap(greyBitmap, 0, 0, greyPaint);
canvas.drawColor(0x99000000);
if (shouldDrawOpening) {
canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, 50, paint);
}
}
Bitmap greyBitmap;
Paint greyPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Bitmap drawBitmap(Bitmap original) {
Bitmap bitmap = Bitmap.createBitmap(original.getWidth(),
original.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(
getColorMatrix()));
paint.setMaskFilter(new BlurMaskFilter(200, BlurMaskFilter.Blur.NORMAL));
canvas.drawBitmap(original, 0, 0, paint);
return bitmap;
}
private ColorMatrix getColorMatrix() {
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
return colorMatrix;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean isPeekThrough = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
isPeekThrough = true;
mCenterPoint.set(event.getX(), event.getY());
invalidate();
break;
}
return isPeekThrough || super.onTouchEvent(event);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mCenterPoint.set(getWidth() / 2.0f, getHeight() / 2.0f);
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/SimpleTab.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.StringRes;
import android.support.v4.app.FragmentTabHost;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by aven on 5/3/15.
*/
public class SimpleTab {
SparseArray- mTabItems = new SparseArray<>();
ViewGroup mContentView;
public void injectInto(ViewGroup parent) {
parent.addView(mContentView);
}
public interface OnTabClickListener {
void onItemClick(View view, Item item, int position);
}
public static class Item {
private CharSequence mLabel;
private Drawable mIcon;
private int mLayoutId = -1;
private int mLabelId = -1;
private int mIconId = -1;
private Decorate mDecorate;
public Item setLabelWithIcon(@StringRes int label, @DrawableRes int
drawable) {
mLabelId = label;
mIconId = drawable;
return this;
}
public Item setCustomItemLayout(int layout, Decorate decorate) {
mLayoutId = layout;
mDecorate = decorate;
return this;
}
}
public interface Decorate {
void onBind(View contentView);
}
private static abstract class SimpleListener implements View.OnClickListener {
int mPosition;
public SimpleListener(int position) {
mPosition = position;
}
}
public static class Builder {
Context mContext;
List
- mItems;
OnTabClickListener mListener;
public Builder(Context context) {
mContext = context;
mItems = new ArrayList
- ();
}
protected SimpleTab bind(SimpleTabLayout layout) {
final SimpleTab tab = new SimpleTab();
tab.mContentView = layout;
init(tab);
return tab;
}
public SimpleTab create(@LayoutRes int... layout) {
final SimpleTab tab = new SimpleTab();
if (layout != null && layout.length > 0) {
tab.mContentView = (ViewGroup) View.inflate(mContext, layout[0], null);
} else {
LinearLayout tabLayout = new LinearLayout(mContext);
tabLayout.setOrientation(LinearLayout.HORIZONTAL);
tab.mContentView = tabLayout;
}
init(tab);
return tab;
}
private void init(final SimpleTab tab) {
for (int i = 0; i < mItems.size(); i++) {
Item item = mItems.get(i);
tab.mTabItems.put(i, item);
if (item.mLayoutId != -1) {
View itemView = View.inflate(mContext, item.mLayoutId, null);
tab.mContentView.addView(itemView);
if (item.mDecorate != null) {
item.mDecorate.onBind(itemView);
}
} else {
TextView label = new TextView(mContext);
label.setText(item.mLabelId);
label.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams
(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams
.WRAP_CONTENT);
layoutParams.weight = 1;
label.setCompoundDrawablesWithIntrinsicBounds(0, item.mIconId, 0, 0);
tab.mContentView.addView(label, layoutParams);
}
}
int count = tab.mContentView.getChildCount();
for (int i = 0; i < count; i++) {
View view = tab.mContentView.getChildAt(i);
view.setOnClickListener(new SimpleListener(i) {
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onItemClick(v, tab.mTabItems.get(mPosition), mPosition);
}
}
});
}
}
public Builder newItem(Item item, int... position) {
if (position != null && position.length > 0) {
mItems.add(position[0], item);
} else {
mItems.add(item);
}
return this;
}
public Builder setOnTabClickListener(OnTabClickListener listener) {
mListener = listener;
return this;
}
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/SimpleTabLayout.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toast;
import net.avenwu.support.util.ViewCompat;
/**
* Created by aven on 5/3/15.
*/
public class SimpleTabLayout extends LinearLayout {
SimpleTab mSimpleTab;
public SimpleTabLayout(Context context) {
this(context, null);
}
public SimpleTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mSimpleTab = new SimpleTab.Builder(getContext())
.newItem(new SimpleTab.Item().setLabelWithIcon(android.R.string.no,
android.R.drawable.ic_delete))
.newItem(new SimpleTab.Item().setLabelWithIcon(android.R.string.no,
android.R.drawable.ic_delete))
.newItem(new SimpleTab.Item().setLabelWithIcon(android.R.string.no,
android.R.drawable.ic_delete))
.newItem(new SimpleTab.Item().setLabelWithIcon(android.R.string.no,
android.R.drawable.ic_delete))
.setOnTabClickListener(new SimpleTab.OnTabClickListener() {
@Override
public void onItemClick(View view, SimpleTab.Item item, int position) {
ViewCompat.makeText(getContext(), "Click " + position, Toast.LENGTH_SHORT).show();
}
})
.bind(this);
}
}
================================================
FILE: support/src/main/java/net/avenwu/support/widget/TagFlowLayout.java
================================================
package net.avenwu.support.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.os.Build;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.util.Xml;
import android.view.Gravity;
import android.view.InflateException;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
* Created by chaobin on 1/14/15.
*/
public class TagFlowLayout extends ViewGroup
implements TextWatcher, View.OnKeyListener, View.OnClickListener {
private static final int INVALID_VALUE = -1;
private SparseIntArray mCachedPosition = new SparseIntArray();
private EditText mInputView;
/**
* Comma in both English & Chinese
*/
private char[] mKeyChar = new char[]{',', ','};
/**
* Key event of enter, comma
*/
private int[] mKeyCode = new int[]{
KeyEvent.KEYCODE_ENTER,
KeyEvent.KEYCODE_COMMA,
KeyEvent.KEYCODE_NUMPAD_COMMA
};
private int mCheckIndex = INVALID_VALUE;
private Decorator mDecorator;
public TagFlowLayout(Context context) {
this(context, null);
}
public TagFlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mInputView = new EditText(context);
mInputView.setBackgroundColor(Color.TRANSPARENT);
mInputView.addTextChangedListener(this);
mInputView.setOnKeyListener(this);
mInputView.setPadding(0, 0, 0, 0);
mInputView.setMinWidth(20);
mInputView.setSingleLine();
mInputView.setGravity(Gravity.CENTER_VERTICAL);
setDecorator(new SimpleDecorator(context));
addView(mInputView);
setOnClickListener(this);
previewInEditMode();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int maxWidth = 0;
int maxHeight = 0;
int width = 0;
int height = 0;
mCachedPosition.clear();
final int widthSpace = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft()
- getPaddingRight();
final int count = getChildCount();
int verticalCount = 0;
for (int i = 0; i < count; ++i) {
final View child = getChildAt(i);
if (nullChildView(child)) {
continue;
}
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidthSpace = lp.leftMargin + child.getMeasuredWidth() + lp.rightMargin;
int childHeightSpace = lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
if (maxWidth + childWidthSpace > widthSpace) {
maxWidth = childWidthSpace;
maxHeight = Math.max(maxHeight, childHeightSpace);
height += maxHeight;
mCachedPosition.put(i, ++verticalCount);
} else {
maxWidth += childWidthSpace;
maxHeight = childHeightSpace;
}
width = Math.max(width, maxWidth);
}
height += maxHeight + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(getImprovedSize(widthMeasureSpec, width),
getImprovedSize(heightMeasureSpec, height));
}
private boolean nullChildView(View child) {
return child == null || child.getVisibility() == GONE;
}
private int getImprovedSize(int measureSpec, int size) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
return size;
case MeasureSpec.EXACTLY:
return specSize;
}
return specSize;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childTop = getPaddingTop();
int childLeft = getPaddingLeft();
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (nullChildView(child)) {
continue;
}
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
if (mCachedPosition.get(i, INVALID_VALUE) != INVALID_VALUE) {
childTop += lp.topMargin + childHeight + lp.bottomMargin;
childLeft = getPaddingLeft();
} else if (childTop == getPaddingTop()) {
childTop += lp.topMargin;
}
childLeft += lp.leftMargin;
setChildFrame(child, childLeft, childTop, childWidth, childHeight);
childLeft += childWidth + lp.rightMargin;
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
MarginLayoutParams params = new MarginLayoutParams(p);
if (mDecorator != null) {
params.setMargins(mDecorator.getMargin()[0], mDecorator.getMargin()[1],
mDecorator.getMargin()[2], mDecorator.getMargin()[3]);
}
return params;
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
MarginLayoutParams params = new MarginLayoutParams(getContext(), attrs);
if (mDecorator != null) {
params.setMargins(mDecorator.getMargin()[0], mDecorator.getMargin()[1],
mDecorator.getMargin()[2], mDecorator.getMargin()[3]);
}
return params;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
MarginLayoutParams params = new MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
mDecorator != null ? mDecorator.getHeight() : ViewGroup.LayoutParams.WRAP_CONTENT);
if (mDecorator != null) {
params.setMargins(mDecorator.getMargin()[0], mDecorator.getMargin()[1],
mDecorator.getMargin()[2], mDecorator.getMargin()[3]);
}
return params;
}
/**
* set the keyCode list which trigger the TAG generation, the default keyCode are
* {@link android.view.KeyEvent#KEYCODE_ENTER},{@link android.view.KeyEvent#KEYCODE_COMMA}
*
* @param keyChar comma on mobile are not always keyCode, null will be ignored
* @param keyCode keyCode defined in {@link android.view.KeyEvent}, null will be ignored
*/
public void setActionKeyCode(char[] keyChar, int... keyCode) {
if (keyChar != null && keyChar.length > 0) {
mKeyChar = keyChar;
}
if (keyCode != null && keyCode.length > 0) {
mKeyCode = keyCode;
}
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (isKeyCodeHit(keyCode)) {
if (!TextUtils.isEmpty(mInputView.getText().toString())) {
generateTag(mInputView.getText().toString());
}
return true;
} else if (KeyEvent.KEYCODE_DEL == keyCode) {
if (TextUtils.isEmpty(mInputView.getText().toString())) {
deleteTag();
return true;
}
}
}
return false;
}
private boolean isKeyCodeHit(int keyCode) {
if (mKeyCode != null && mKeyCode.length > 0) {
for (int key : mKeyCode) {
if (key == keyCode) {
return true;
}
}
}
return false;
}
private boolean isKeyCharHit(char keyChar) {
if (mKeyChar != null && mKeyChar.length > 0) {
for (char key : mKeyChar) {
if (key == keyChar) {
return true;
}
}
}
return false;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.length() > 0) {
if (isKeyCharHit(s.charAt(0))) {
mInputView.setText("");
} else if (isKeyCharHit(s.charAt(s.length() - 1))) {
mInputView.setText("");
generateTag(s.subSequence(0, s.length() - 1) + "");
}
}
}
@Override
public void onClick(View v) {
if (v instanceof TagFlowLayout) {
mInputView.requestFocus();
InputMethodManager m = (InputMethodManager) getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
m.showSoftInput(mInputView, InputMethodManager.SHOW_FORCED);
// clear check status
if (mCheckIndex != INVALID_VALUE) {
updateCheckStatus(getChildAt(mCheckIndex), false);
mCheckIndex = INVALID_VALUE;
}
return;
}
final int index = indexOfChild(v);
// skip unnecessary setting
if (index != mCheckIndex) {
mCheckIndex = index;
updateCheckStatus(v, true);
for (int i = 0; i < index; i++) {
updateCheckStatus(getChildAt(i), false);
}
//skip input box
for (int i = index + 1; i < getChildCount() - 1; i++) {
updateCheckStatus(getChildAt(i), false);
}
}
}
private void deleteTag() {
if (getChildCount() > 1) {
removeViewAt(mCheckIndex == INVALID_VALUE ? indexOfChild(mInputView) - 1 : mCheckIndex);
mCheckIndex = INVALID_VALUE;
mInputView.requestFocus();
}
}
/**
* Auto generate the last input content as tag label
*/
public void autoComplete() {
if (!TextUtils.isEmpty(mInputView.getText().toString())) {
generateTag(null);
}
}
/**
* can be null
*/
private void generateTag(CharSequence tag) {
CharSequence tagString = tag == null ? mInputView.getText().toString() : tag;
mInputView.getText().clear();
final int targetIndex = indexOfChild(mInputView);
TextView tagLabel;
if (mDecorator.getLayout() != INVALID_VALUE) {
View view = View.inflate(getContext(), mDecorator.getLayout(), null);
if (view instanceof TextView) {
tagLabel = (TextView) view;
} else {
throw new IllegalArgumentException(
"The custom layout for tagLabel label must have TextView as root element");
}
} else {
tagLabel = new TextView(getContext());
tagLabel.setPadding(mDecorator.getPadding()[0], mDecorator.getPadding()[1],
mDecorator.getPadding()[2], mDecorator.getPadding()[3]);
tagLabel.setTextSize(mDecorator.getTextSize());
}
updateCheckStatus(tagLabel, false);
tagLabel.setText(tagString);
tagLabel.setSingleLine();
tagLabel.setGravity(Gravity.CENTER_VERTICAL);
tagLabel.setEllipsize(TextUtils.TruncateAt.END);
if (mDecorator.getMaxLength() != INVALID_VALUE) {
InputFilter maxLengthFilter = new InputFilter.LengthFilter(mDecorator.getMaxLength());
tagLabel.setFilters(new InputFilter[]{maxLengthFilter});
}
tagLabel.setClickable(true);
tagLabel.setOnClickListener(this);
addView(tagLabel, targetIndex);
mInputView.requestFocus();
}
private void updateCheckStatus(View view, boolean checked) {
if (view == null) {
return;
}
// don't reuse drawable for different tag label
Drawable[] drawables = mDecorator.getBackgroundDrawable();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
view.setBackgroundDrawable(drawables[drawables.length > 1 ? (checked ? 1 : 0) : 0]);
} else {
view.setBackground(drawables[drawables.length > 1 ? (checked ? 1 : 0) : 0]);
}
final int[] color = mDecorator.getTextColor();
if (color != null && color.length > 0) {
((TextView) view).setTextColor((color[color.length > 1 ? (checked ? 1 : 0) : 0]));
}
}
private void previewInEditMode() {
if (isInEditMode()) {
generateTag("Hot Tag Ckecked");
generateTag("TAG A");
generateTag("TAG B");
generateTag("TAG C");
updateCheckStatus(getChildAt(0), true);
mInputView.setText("Input here...");
}
}
public void setDecorator(Decorator decorator) {
if (decorator != null) {
mDecorator = decorator;
try {
setInnerAttribute();
} catch (Exception e) {
e.printStackTrace();
throw new UnknownError(e.getMessage() + "\n unavailable to setDecorator");
}
}
}
/**
* Make sure the input EditView looks like TextView label
*/
private void setInnerAttribute()
throws XmlPullParserException, IOException, ClassNotFoundException {
mInputView.setTextSize(mDecorator.getTextSize());
if (mDecorator.getMaxLength() != INVALID_VALUE) {
InputFilter maxLengthFilter = new InputFilter.LengthFilter(mDecorator.getMaxLength());
mInputView.setFilters(new InputFilter[]{maxLengthFilter});
}
if (mDecorator.getTextColor() != null && mDecorator.getTextColor().length > 1) {
mInputView.setTextColor(mDecorator.getTextColor()[0]);
}
if (mDecorator.getLayout() != INVALID_VALUE) {
XmlResourceParser parser = getResources().getLayout(mDecorator.getLayout());
final AttributeSet set = Xml.asAttributeSet(parser);
int type = 0;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if ("TextView".equals(name) || Class.forName(name).isInstance(TextView.class)) {
int[] attr = new int[]{
android.R.attr.layout_width,
android.R.attr.layout_height,
};
TypedArray array = getContext().obtainStyledAttributes(set, attr);
final int height = array.getDimensionPixelSize(1, 0);
if (height != 0) {
MarginLayoutParams layoutParams = (MarginLayoutParams) mInputView
.getLayoutParams();
layoutParams.height = height;
}
//TODO other useful attribute
array.recycle();
} else {
throw new InflateException(parser.getPositionDescription()
+ ": Only TextView or subclass of TextView is supported!");
}
}
}
public CharSequence[] getTagArray() {
final int count = getChildCount();
if (count > 1) {
CharSequence[] tags = new CharSequence[count - 1];
for (int i = 0; i < count - 1; i++) {
View child = getChildAt(i);
if (child instanceof TextView) {
tags[i] = ((TextView) child).getText();
}
}
return tags;
}
return new CharSequence[]{};
}
public void setTagArray(CharSequence... tags) {
for (CharSequence tag : tags) {
generateTag(tag);
}
}
public void clearTags() {
while (getChildCount() > 1) {
removeAllViews();
addView(mInputView);
}
}
public void setInputable(boolean enable) {
mInputView.setVisibility(enable ? View.VISIBLE : View.GONE);
mInputView.setEnabled(enable);
}
/**
* Implements your own Decorator to custom the tag view
*/
interface Decorator {
/**
* Size in unit of sp
*/
public int getTextSize();
/**
* Padding on mLeftView, top, right, bottom in unit of dip
*/
public int[] getPadding();
/**
* Margin on mLeftView, top, right, bottom in unit of dip
*/
public int[] getMargin();
/**
* Color in format of AARRGGBB
*/
public int[] getTextColor();
/**
* Tag view's background can be satisfied either by color or drawable resources,
*/
public Drawable[] getBackgroundDrawable();
/**
* Size in unit of dip, if you provide custom layout by {@link #getLayout()}, it must have
* the same height value
*/
public int getHeight();
/**
* Provide your own layout id so you can custom the tag view what ever you like,
* keep in mind the layout must have TextView as root element
*
* @return -1 will be ignored
*/
public int getLayout();
/**
* @return
*/
public int getMaxLength();
}
/**
* Default decorator which will set on the TAG view
*/
public static class SimpleDecorator implements Decorator {
protected int textSize;
protected int[] textColor;
protected int[] padding;
protected int[] margin;
protected int mTagHeight;
protected int mRadius;
public SimpleDecorator(Context context) {
textSize = 14;
final int p = getPixelSize(context, TypedValue.COMPLEX_UNIT_DIP, 5);
padding = new int[]{p, p, p, p};
final int m = getPixelSize(context, TypedValue.COMPLEX_UNIT_DIP, 2);
margin = new int[]{m, m, m, m};
mRadius = getPixelSize(context, TypedValue.COMPLEX_UNIT_DIP, 5);
mTagHeight = getPixelSize(context, TypedValue.COMPLEX_UNIT_DIP, 30);
textColor = new int[]{0xFF000000, 0xFFFFFFFF};
}
private int getPixelSize(Context context, int unit, int size) {
return (int) TypedValue
.applyDimension(unit, size, context.getResources().getDisplayMetrics());
}
@Override
public int getTextSize() {
return textSize;
}
@Override
public int[] getPadding() {
return padding;
}
@Override
public int[] getMargin() {
return margin;
}
@Override
public int[] getTextColor() {
return textColor;
}
public int getHeight() {
return mTagHeight;
}
public Drawable[] getBackgroundDrawable() {
return new Drawable[]{
newRoundRectShape(0xFF4285f4, mRadius),
newRoundRectShape(0xFF3f51b5, mRadius)
};
}
@Override
public int getLayout() {
return INVALID_VALUE;
}
@Override
public int getMaxLength() {
return 20;
}
protected Drawable newRoundRectShape(int color, int radius) {
ShapeDrawable shape = new ShapeDrawable(new RoundRectShape(new float[]{radius, radius,
radius, radius, radius, radius, radius, radius}, null, null));
shape.getPaint().setStyle(Paint.Style.FILL);
shape.getPaint().setColor(color);
shape.getPaint().setAntiAlias(true);
return shape;
}
}
}
================================================
FILE: support/src/main/res/drawable/toast_shape.xml
================================================
================================================
FILE: support/src/main/res/layout/custom_toast.xml
================================================
================================================
FILE: support/src/main/res/values/attr_breath_light.xml
================================================
================================================
FILE: support/src/main/res/values/attr_dim_imagview.xml
================================================
================================================
FILE: support/src/main/res/values/attr_ex_textview.xml
================================================
================================================
FILE: support/src/main/res/values/attr_flat_tab_group.xml
================================================
================================================
FILE: support/src/main/res/values/attrs_drawer_frame.xml
================================================
================================================
FILE: support/src/main/res/values/ids.xml
================================================
================================================
FILE: support/src/main/res/values/strings.xml
================================================
Support