Skip to content

Instantly share code, notes, and snippets.

@aashreys
Last active July 3, 2017 07:00
Show Gist options
  • Save aashreys/565c47c76a4eb8d7444478ab16e1f984 to your computer and use it in GitHub Desktop.
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.
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