Last active
July 3, 2017 07:00
-
-
Save aashreys/565c47c76a4eb8d7444478ab16e1f984 to your computer and use it in GitHub Desktop.
A simple view for providing iOS's Assistive Touch capabilities to your application's screens.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.animation.Animator; | |
import android.animation.AnimatorSet; | |
import android.animation.ObjectAnimator; | |
import android.content.Context; | |
import android.content.res.TypedArray; | |
import android.os.Build; | |
import android.support.annotation.ColorRes; | |
import android.support.annotation.DrawableRes; | |
import android.support.annotation.RequiresApi; | |
import android.util.AttributeSet; | |
import android.util.Property; | |
import android.view.Gravity; | |
import android.view.MotionEvent; | |
import android.view.View; | |
import android.widget.FrameLayout; | |
import android.widget.ImageView; | |
import android.widget.Toast; | |
import static android.os.Build.VERSION_CODES.M; | |
/** | |
* Created by aashreys on 11/25/2016. | |
*/ | |
public class QuickActionView extends FrameLayout | |
{ | |
private int size, padding; | |
private boolean isDragging, isExpanded; | |
private static final String TAG = QuickActionView.class.getSimpleName(); | |
public QuickActionView(Context context) | |
{ | |
super(context); | |
_init(context, null, 0, 0); | |
} | |
public QuickActionView(Context context, AttributeSet attrs) | |
{ | |
super(context, attrs); | |
_init(context, attrs, 0, 0); | |
} | |
public QuickActionView(Context context, AttributeSet attrs, int defStyleAttr) | |
{ | |
super(context, attrs, defStyleAttr); | |
_init(context, attrs, defStyleAttr, 0); | |
} | |
@RequiresApi (api = Build.VERSION_CODES.LOLLIPOP) | |
public QuickActionView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) | |
{ | |
super(context, attrs, defStyleAttr, defStyleRes); | |
_init(context, attrs, defStyleAttr, defStyleRes); | |
} | |
private void _init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) | |
{ | |
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.QuickActionView, 0, 0); | |
try { | |
size = (int) a.getDimension(R.styleable.QuickActionView_qav_button_size, 0); | |
padding = (int) a.getDimension(R.styleable.QuickActionView_qav_button_padding, 0); | |
} | |
finally { | |
a.recycle(); | |
} | |
final ActionView defaultView = new ActionView(getContext()); | |
defaultView.setAction(new Action(Action.DIRECTION_NONE, new OnClickListener() { | |
@Override | |
public void onClick(View view) | |
{ | |
if (!isExpanded) { | |
expand(); | |
} | |
else { | |
collapse(); | |
} | |
} | |
}, -1, -1, "Quick Actions")); | |
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(size, size); | |
params.gravity = Gravity.RIGHT | Gravity.BOTTOM; | |
defaultView.setPadding(padding, padding, padding, padding); | |
addView(defaultView, 0, params); | |
defaultView.setOnLongClickListener(new OnLongClickListener() { | |
@Override | |
public boolean onLongClick(View view) | |
{ | |
isDragging = true; | |
collapse(); | |
setChildrenVisibility(INVISIBLE); | |
return true; | |
} | |
}); | |
defaultView.setOnTouchListener(new OnTouchListener() { | |
@Override | |
public boolean onTouch(View view, MotionEvent event) | |
{ | |
if (isDragging) { | |
if (event.getAction() == MotionEvent.ACTION_MOVE) { | |
defaultView.setX(getBoundedX(event) - size / 2); | |
defaultView.setY(getBoundedY(event) - size / 2); | |
return true; | |
} | |
if (event.getAction() == MotionEvent.ACTION_UP) { | |
isDragging = false; | |
moveChildren(defaultView.getX(), defaultView.getY()); | |
setChildrenVisibility(VISIBLE); | |
return true; | |
} | |
} | |
return false; | |
} | |
}); | |
} | |
private float getBoundedX(MotionEvent event) | |
{ | |
int[] viewXY = new int[2]; | |
getLocationOnScreen(viewXY); | |
float x = event.getRawX() - viewXY[0]; | |
float boundedLeft = size / 2; | |
float boundedRight = getWidth() - (size / 2); | |
if (x < boundedLeft) { | |
return boundedLeft; | |
} | |
else if (x > boundedRight) { | |
return boundedRight; | |
} | |
else { | |
return x; | |
} | |
} | |
private float getBoundedY(MotionEvent event) | |
{ | |
int[] viewXY = new int[2]; | |
getLocationOnScreen(viewXY); | |
float y = event.getRawY() - viewXY[1]; | |
float boundedTop = size / 2; | |
float boundedBottom = getHeight() - (size / 2); | |
if (y < (boundedTop)) { | |
return boundedTop; | |
} | |
else if (y > boundedBottom) { | |
return boundedBottom; | |
} | |
else { | |
return y; | |
} | |
} | |
@Override | |
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { | |
super.onLayout(changed, left, top, right, bottom); | |
} | |
private void setChildrenVisibility(int visibility) | |
{ | |
for (int i = 0; i < getChildCount() - 1; i++) { | |
getChildAt(i).setVisibility(visibility); | |
} | |
} | |
private void moveChildren(float x, float y) | |
{ | |
for (int i = 0; i < getChildCount() - 1; i++) { | |
View view = getChildAt(i); | |
view.setX(x); | |
view.setY(y); | |
} | |
} | |
@Override | |
public void addView(View child) | |
{ | |
if (child instanceof ActionView) { | |
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(size, size); | |
params.gravity = Gravity.RIGHT | Gravity.BOTTOM; | |
child.setPadding(padding, padding, padding, padding); | |
addView(child, 0, params); | |
View topButton = getChildAt(getChildCount() - 1); | |
child.setX(topButton.getX()); | |
child.setY(topButton.getY()); | |
} | |
else { | |
throw new IllegalArgumentException("Child view must be instance of ActionView"); | |
} | |
} | |
public void addAction(Action action) | |
{ | |
ActionView actionView = new ActionView(getContext()); | |
actionView.setAction(action); | |
addView(actionView); | |
} | |
public void expand() | |
{ | |
animateActions(true); | |
isExpanded = true; | |
} | |
public void collapse() | |
{ | |
animateActions(false); | |
isExpanded = false; | |
} | |
public void setButtonDrawable(@DrawableRes int drawable) | |
{ | |
ActionView view = (ActionView) getChildAt(getChildCount() - 1); | |
view.setImageResource(drawable); | |
} | |
public void setButtonBackground(@ColorRes int color) | |
{ | |
ActionView view = (ActionView) getChildAt(getChildCount() - 1); | |
if (Build.VERSION.SDK_INT >= M) { | |
view.setBackgroundColor(getResources().getColor(color, null)); | |
} | |
else { | |
view.setBackgroundColor(getResources().getColor(color)); | |
} | |
} | |
private void animateActions(boolean isExpanding) | |
{ | |
float dispX, dispY; | |
if (isExpanding) { | |
dispX = dispY = size; | |
} | |
else { | |
dispX = dispY = 0; | |
} | |
int childCount = getChildCount(); | |
float refX = getChildAt(childCount - 1).getX(); | |
float refY = getChildAt(childCount - 1).getY(); | |
int[] numItemsAnimated = new int[4]; // Array elements represent number of items animated in left, top, right, | |
// bottom | |
// direction. | |
Animator[] animators = new Animator[childCount - 1]; | |
for (int i = 0; i < childCount - 1; i++) { | |
ActionView target = (ActionView) getChildAt(i); | |
float displacement, refValue = 0, finalValue = 0; | |
Property animProperty; | |
boolean isDisplacementHorizontal = false; | |
if (target.direction != Action.DIRECTION_NONE) { | |
isDisplacementHorizontal = target.direction == Action.DIRECTION_LEFT | |
|| target.direction == Action.DIRECTION_RIGHT; | |
boolean isDisplacementPositive = target.direction == Action.DIRECTION_RIGHT | |
|| target.direction == Action.DIRECTION_BOTTOM; | |
numItemsAnimated[target.direction] += 1; | |
animProperty = isDisplacementHorizontal ? X : Y; | |
displacement = (isDisplacementHorizontal ? (dispX) : (dispY)) * numItemsAnimated[target.direction] | |
* (isDisplacementPositive ? 1 : -1); | |
} | |
else { | |
displacement = 0; | |
animProperty = X; | |
} | |
refValue = isDisplacementHorizontal ? refX : refY; | |
finalValue = refValue + displacement; | |
animators[i] = ObjectAnimator.ofFloat(target, animProperty, finalValue); | |
} | |
AnimatorSet animatorSet = new AnimatorSet(); | |
animatorSet.playTogether(animators); | |
animatorSet.setDuration(150); | |
animatorSet.start(); | |
} | |
public static class Action | |
{ | |
public static final int DIRECTION_NONE = -1, DIRECTION_LEFT = 0, DIRECTION_TOP = 1, DIRECTION_RIGHT = 2, | |
DIRECTION_BOTTOM = 3; | |
private String name; | |
private int direction; | |
private OnClickListener listener; | |
private @DrawableRes int drawableRes; | |
private @ColorRes int backgroundColorRes; | |
public Action(int direction, OnClickListener listener, int drawableRes, int backgroundColorRes, String name) | |
{ | |
this.direction = direction; | |
this.listener = listener; | |
this.drawableRes = drawableRes; | |
this.backgroundColorRes = backgroundColorRes; | |
this.name = name; | |
} | |
} | |
public static class ActionView extends ImageView | |
{ | |
private int direction; | |
public ActionView(Context context) | |
{ | |
super(context); | |
_init(context, null, 0); | |
} | |
public ActionView(Context context, AttributeSet attrs) | |
{ | |
super(context, attrs); | |
_init(context, attrs, 0); | |
} | |
public ActionView(Context context, AttributeSet attrs, int defStyleAttr) | |
{ | |
super(context, attrs, defStyleAttr); | |
_init(context, attrs, defStyleAttr); | |
} | |
private void _init(Context context, AttributeSet attrs, int defStyleAttr) | |
{ | |
setScaleType(ScaleType.FIT_CENTER); | |
} | |
private void setAction(final Action action) | |
{ | |
this.direction = action.direction; | |
if (action.drawableRes != -1) { | |
setImageResource(action.drawableRes); | |
} | |
if (action.backgroundColorRes != -1) { | |
if (Build.VERSION.SDK_INT >= M) { | |
setBackgroundColor(getResources().getColor(action.backgroundColorRes, null)); | |
} | |
else { | |
setBackgroundColor(getResources().getColor(action.backgroundColorRes)); | |
} | |
} | |
setOnClickListener(action.listener); | |
setOnLongClickListener(new OnLongClickListener() { | |
@Override | |
public boolean onLongClick(View view) | |
{ | |
Toast.makeText(getContext(), action.name, Toast.LENGTH_SHORT).show(); | |
return true; | |
} | |
}); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment