• Tutorials
  • Tips & Tricks
  • Applications
  • News

Android Trainee

  • Tutorials
  • Tips & Tricks
  • Applications
  • News
Home  /  Tutorials  /  Android Circle Menu With Animation.
10 July 2015

Android Circle Menu With Animation.

Written by admin@androidtrainee
Tutorials android circle, Android Circle Animation, android L, android L Circle Animation, android lollipop, circle Animation Leave a Comment

Android Circle Menu With Animation.

 

Creat Android FilterMenu-lib Project.

—>  AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.linroid.filtermenu.library" >

</manifest>

attrs.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FilterMenuLayout">
<attr name="fm_primaryColor" format="color|reference"/>
<attr name="fm_primaryDarkColor" format="color|reference"/>
<attr name="fm_expandedRadius" format="dimension"/>
<attr name="fm_collapsedRadius" format="dimension"/>
<attr name="fm_centerLeft" format="dimension"/>
<attr name="fm_centerRight" format="dimension"/>
<attr name="fm_centerTop" format="dimension"/>
<attr name="fm_centerHorizontal" format="boolean"/>
<attr name="fm_centerVertical" format="boolean"/>
<attr name="fm_centerBottom" format="dimension"/>
<attr name="fm_sweepAngle" format="integer"/>
</declare-styleable>
</resources>

IN Layout folder

menu_item.xml


<?xml version="1.0" encoding="utf-8"?>
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:background="@drawable/bg_menu_item">
</ImageButton>

 

In drawable folder

bg_circle_pressed.xml

 


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#99FFFFFF"/>
</shape>

bg_menu_item.xml


<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/transparent" android:state_pressed="false">
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="translationZ"
android:valueFrom="4dp"
android:valueTo="0dp"
android:valueType="floatType" />
</item>
<item android:drawable="@drawable/bg_circle_pressed" android:state_pressed="true">
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="translationZ"
android:valueFrom="0dp"
android:valueTo="4dp"
android:valueType="floatType" />
</item>
<item android:drawable="@drawable/bg_circle_pressed" android:state_focused="true"/>
</selector>

 

put cancle imagein drawable folder.

ic_cancel

 

 

ic_cancelIN Java File.

FilterMenu.java


 

package com.linroid.filtermenu.library;

import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageButton;
import android.widget.PopupMenu;

import java.util.ArrayList;
import java.util.List;


/**
* Created by linroid on 15/3/8.
*/
public class FilterMenu implements IMenu {

private List<Item> items = new ArrayList<>();
private OnMenuChangeListener listener;
private FilterMenuLayout layout;
//    /**
//     * add menu item to layout
//     *
//     * @param item
//     * @param listener
//     */
//    public void addItem(Item item, View.OnClickListener listener) {
//        items.add(item);
//    }

public List<Item> getItems() {
return items;
}

public void setItems(List<Item> items) {
this.items = items;
}

public OnMenuChangeListener getListener() {
return listener;
}

public void setListener(OnMenuChangeListener listener) {
this.listener = listener;
for (final Item item : getItems()) {
item.getView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("FilterMenu", "onClick");
if (getListener() != null) {
getListener().onMenuItemClick(item.getView(), item.getPosition());
}
if (layout != null) {
layout.collapse(true);
}
}
});
}
}

@Override
public void collapse(boolean animate) {
layout.collapse(animate);
}

@Override
public void expand(boolean animate) {
layout.expand(animate);
}

@Override
public void toggle(boolean animate) {
layout.toggle(animate);
}

@Override
public void setMenuLayout(FilterMenuLayout view) {
this.layout = view;
if (view == null) {
return;
}
for (final Item item : getItems()) {
layout.addView(item.getView());
}
layout.setMenu(this);
}

public static interface OnMenuChangeListener {
void onMenuItemClick(View view, int position);

void onMenuCollapse();

void onMenuExpand();
}

public static class Builder {
OnMenuChangeListener listener;
private List<Item> items = new ArrayList<>();
private Context ctx;
private LayoutInflater inflater;
private FilterMenuLayout layout;

public Builder(Context ctx) {
this.ctx = ctx;
this.inflater = LayoutInflater.from(ctx);
}

public Builder withListener(OnMenuChangeListener listener) {
this.listener = listener;
return this;
}

public Builder inflate(int menuResId) {
PopupMenu popupMenu = new PopupMenu(ctx, null);
popupMenu.inflate(menuResId);
Menu menu = popupMenu.getMenu();
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
addItem(item.getIcon());
}
menu.clear();
menu = null;
popupMenu = null;
return this;
}

public Builder addItem(Drawable icon) {
ImageButton view = (ImageButton) inflater.inflate(R.layout.menu_item, null, false);
view.setImageDrawable(icon);
//            TypedValue value = new TypedValue();
//            ctx.getTheme().resolveAttribute(R.attr.selectableItemBackgroundBorderless, value, true);
//            view.setBackgroundResource(value.resourceId);
addItem(view);
return this;
}

public Builder addItem(int iconResId) {
Drawable icon = ctx.getResources().getDrawable(iconResId);
addItem(icon);
return this;
}

public Builder addItem(View customView) {
Item item = new Item();
item.setView(customView);
item.setPosition(items.size());
item.getView().setTag(item);
items.add(item);

return this;
}

public Builder attach(FilterMenuLayout view) {
this.layout = view;
return this;
}

public FilterMenu build() {
FilterMenu menu = new FilterMenu();
menu.setItems(items);
menu.setListener(this.listener);
menu.setMenuLayout(this.layout);
return menu;
}
}

public static class Item {
private View view;
private int x;
private int y;
private int position;
private Rect bounds = new Rect(0, 0, 0, 0);

public View getView() {
return view;
}

public void setView(View view) {
this.view = view;
view.setAlpha(0f);
}

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

public int getPosition() {
return position;
}

public void setPosition(int position) {
this.position = position;
}

public void setBounds(int left, int top, int right, int bottom) {
this.bounds.set(left, top, right, bottom);
}

public Rect getBounds() {
return bounds;
}

public void setBounds(Rect bounds) {
this.bounds = bounds;
}
}
}

FilterMenuDrawable.java


package com.linroid.filtermenu.library;

import android.content.Context;
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.drawable.Drawable;

/**
* Created by linroid on 15/3/10.
*/
public class FilterMenuDrawable extends Drawable {
private Context ctx;
private Paint paint;
private IconState state = IconState.COLLAPSED;
private int radius;

private int lineWidth = 8;
private float expandProgress = 0;

public FilterMenuDrawable(Context ctx, int color, int radius) {
this.ctx = ctx;
this.radius = radius;

paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(color);
paint.setStrokeWidth(lineWidth);

}
public enum IconState{
COLLAPSED,
EXPANDED
}

public float getExpandProgress() {
return expandProgress;
}

public void setExpandProgress(float expandProgress) {
this.expandProgress = expandProgress;
invalidateSelf();
}

@Override
public int getIntrinsicWidth() {
return (int) (radius*0.8f);
}

@Override
public int getIntrinsicHeight() {
return (int) (radius*0.8f);
}

@Override
public void draw(Canvas canvas) {
//draw three line
//        paint.setColor(Color.BLACK);
//        canvas.drawRect(getBounds(), paint);
//        paint.setColor(Color.WHITE);
if(expandProgress<=0.5f){
drawTopLine(canvas, expandProgress);
drawMiddleLine(canvas, expandProgress);
drawBottomLine(canvas, expandProgress);
// draw cancel
}else{
drawTopLeftLine(canvas, expandProgress);
drawBottomLeftLine(canvas, expandProgress);
}
}

private void drawBottomLeftLine(Canvas canvas, float progress) {
int ly = (int) (getBounds().bottom-getIntrinsicHeight()*progress);
int ry = (int) (getBounds().top+ getIntrinsicHeight()*progress);
canvas.drawLine(getBounds().left, ly, getBounds().right, ry, paint);
}

private void drawTopLeftLine(Canvas canvas, float progress) {
int ry = (int) (getBounds().bottom-getIntrinsicHeight()*progress);
int ly = (int) (getBounds().top+ getIntrinsicHeight()*progress);
canvas.drawLine(getBounds().left, ly, getBounds().right, ry, paint);
}


private void drawTopLine(Canvas canvas, float progress) {
int y = getBounds().top + (int) (getIntrinsicHeight()* progress) + lineWidth;
canvas.drawLine(getBounds().left, y, getBounds().left+getIntrinsicWidth(), y, paint);
}

private void drawMiddleLine(Canvas canvas, float progress) {
int y = getBounds().top + getIntrinsicHeight() / 2;
int len = getIntrinsicWidth() /2;
int centerX = getBounds().centerX();
canvas.drawLine(centerX-len/2, y, centerX+len/2, y, paint);
}
private void drawBottomLine(Canvas canvas, float progress) {
int y = getBounds().top + (int) (getIntrinsicHeight() * (1-progress)) - lineWidth;
int len = getIntrinsicWidth() /4;
int centerX = getBounds().centerX();
canvas.drawLine(centerX-len/2, y, centerX+len/2, y, paint);
}

@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}

@Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
}

@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}

FilterMenuLayout.java

 


package com.linroid.filtermenu.library;

import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.animation.OvershootInterpolator;

import java.util.ArrayList;
import java.util.List;


/**
* Created by linroid on 15/3/4.
*/
public class FilterMenuLayout extends ViewGroup {
public static final String TAG = "FilterMenuLayout";

public static final int STATE_COLLAPSE = 0x1;
public static final int STATE_EXPAND = 0x2;

public static final int DURATION = 400;
private static final int DURATION_BETWEEN_ITEM = 50;
/**
* menu items position start angle*
*/
double fromAngle;
/**
* menu items position end angle *
*/
double toAngle;
Point touchPoint = new Point();
boolean inChild = false;
FilterMenu.Item touchedItem;
boolean isExpand = false;
/**
* arc radius when menu is collapsed *
*/
private int collapsedRadius;
/**
* arc radius when menu is expanded *
*/
private int expandedRadius;
private int primaryColor;
/**
* color of inner circle when menu expanded *
*/
private int primaryDarkColor;
/**
* center of circle
*/
private Point center;
private int state = STATE_COLLAPSE;
private Paint primaryPaint;
private Paint primaryDarkPaint;
private OvalOutline outlineProvider;
/**
* the expanded circle bounds*
*/
private Rect menuBounds;
/**
* set the circle position, base on its center , the menu will auto align.You should only set two directions at most.
*/
private int centerLeft, centerRight, centerTop, centerBottom;
/**
* If true, centers the circle horizontally.
*/
private boolean centerHorizontal;
/**
* If true, centers the circle vertically. *
*/
private boolean centerVertical;
/**
* all intersect points *
*/
private List<Point> intersectPoints = new ArrayList<>();
/**
* expand progress *
*/
private float expandProgress = 0;
/**
* the center drawable
* TODO: add more drawable
*/
private FilterMenuDrawable drawable;
private ObjectAnimator circleAnimator;
private ValueAnimator colorAnimator;
private FilterMenu menu;

public FilterMenuLayout(Context context) {
super(context);
init(context, null);
}

public FilterMenuLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}

public FilterMenuLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public FilterMenuLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}

/**
* calculate arc angle between point a and point b
*
* @param center
* @param a
* @param b
* @param area
* @param radius
* @return
*/
private static double arcAngle(Point center, Point a, Point b, Rect area, int radius) {
double angle = threePointsAngle(center, a, b);
Point innerPoint = findMidnormalPoint(center, a, b, area, radius);
Point midInsectPoint = new Point((a.x + b.x) / 2, (a.y + b.y) / 2);
double distance = pointsDistance(midInsectPoint, innerPoint);
if (distance > radius) {
return 360 - angle;
}
return angle;
}

/**
* find the middle point of two intersect points in circle,only one point will be correct
*
* @param center
* @param a
* @param b
* @param area
* @param radius
* @return
*/
private static Point findMidnormalPoint(Point center, Point a, Point b, Rect area, int radius) {
if (a.y == b.y) {
//top
if (a.y < center.y) {
return new Point((a.x + b.x) / 2, center.y + radius);
}
//bottom
return new Point((a.x + b.x) / 2, center.y - radius);
}
if (a.x == b.x) {
//left
if (a.x < center.x) {
return new Point(center.x + radius, (a.y + b.y) / 2);
}
//right
return new Point(center.x - radius, (a.y + b.y) / 2);
}
//slope of line ab
double abSlope = (a.y - b.y) / (a.x - b.x * 1.0);
//slope of midnormal
double midnormalSlope = -1.0 / abSlope;

double radian = Math.tan(midnormalSlope);
int dy = (int) (radius * Math.sin(radian));
int dx = (int) (radius * Math.cos(radian));
Point point = new Point(center.x + dx, center.y + dy);
if (!inArea(point, area, 0)) {
point = new Point(center.x - dx, center.y - dy);
}
return point;
}

private static double pointAngleOnCircle(Point center, Point point, Point coor) {
double angle = threePointsAngle(center, point, coor);
if (point.y < center.y) {
angle = 360 - angle;
}
return angle;
}

/**
* judge if an point in the area or not
*
* @param point
* @param area
* @param offsetRatio
* @return
*/
public static boolean inArea(Point point, Rect area, float offsetRatio) {
int offset = (int) (area.width() * offsetRatio);
return point.x >= area.left - offset && point.x <= area.right + offset &&
point.y >= area.top - offset && point.y <= area.bottom + offset;
}

/**
* calculate the  point a's angle of rectangle consist of point a,point b, point c;
*
* @param vertex
* @param A
* @param B
* @return
*/
private static double threePointsAngle(Point vertex, Point A, Point B) {
double b = pointsDistance(vertex, A);
double c = pointsDistance(A, B);
double a = pointsDistance(B, vertex);

return Math.toDegrees(Math.acos((a * a + b * b - c * c) / (2 * a * b)));

}

/**
* calculate distance of two points
*
* @param a
* @param b
* @return
*/
private static double pointsDistance(Point a, Point b) {
int dx = b.x - a.x;
int dy = b.y - a.y;
return Math.sqrt(dx * dx + dy * dy);
}

private void init(Context ctx, AttributeSet attrs) {
float density = getResources().getDisplayMetrics().density;
TypedArray ta = ctx.obtainStyledAttributes(attrs, R.styleable.FilterMenuLayout);
int defaultCollapsedRadius = (int) (65 / 2.f * density + 0.5);
int defaultExpandedRadius = (int) (65 * 2 * density + 0.5);
collapsedRadius = ta.getDimensionPixelSize(R.styleable.FilterMenuLayout_fm_collapsedRadius, defaultCollapsedRadius);
expandedRadius = ta.getDimensionPixelSize(R.styleable.FilterMenuLayout_fm_expandedRadius, defaultExpandedRadius);


centerLeft = ta.getDimensionPixelSize(R.styleable.FilterMenuLayout_fm_centerLeft, 0);
centerRight = ta.getDimensionPixelSize(R.styleable.FilterMenuLayout_fm_centerRight, 0);
centerTop = ta.getDimensionPixelSize(R.styleable.FilterMenuLayout_fm_centerTop, 0);
centerBottom = ta.getDimensionPixelSize(R.styleable.FilterMenuLayout_fm_centerBottom, 0);
centerHorizontal = ta.getBoolean(R.styleable.FilterMenuLayout_fm_centerHorizontal, false);
centerVertical = ta.getBoolean(R.styleable.FilterMenuLayout_fm_centerVertical, false);

primaryColor = ta.getColor(R.styleable.FilterMenuLayout_fm_primaryColor, getResources().getColor(android.R.color.holo_blue_bright));
primaryDarkColor = ta.getColor(R.styleable.FilterMenuLayout_fm_primaryDarkColor, getResources().getColor(android.R.color.holo_blue_dark));
ta.recycle();

if (!centerHorizontal) {
centerLeft = centerLeft != 0 && centerLeft < collapsedRadius ? collapsedRadius : centerLeft;
centerRight = centerRight != 0 && centerRight < collapsedRadius ? collapsedRadius : centerRight;
if (centerLeft == 0 && centerRight == 0) {
centerLeft = collapsedRadius;
}
}
if (!centerVertical) {
centerTop = centerTop != 0 && centerTop < collapsedRadius ? collapsedRadius : centerTop;
centerBottom = centerBottom != 0 && centerBottom < collapsedRadius ? collapsedRadius : centerBottom;
if (centerTop == 0 && centerBottom == 0) {
centerTop = collapsedRadius;
}
}

center = new Point();
center.set(collapsedRadius, expandedRadius);

if (collapsedRadius > expandedRadius) {
throw new IllegalArgumentException("expandedRadius must bigger than collapsedRadius");
}
primaryPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
primaryPaint.setColor(primaryColor);
primaryPaint.setStyle(Paint.Style.FILL);

primaryDarkPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
primaryDarkPaint.setColor(primaryColor);
primaryDarkPaint.setStyle(Paint.Style.FILL);

setWillNotDraw(false);
if (Build.VERSION.SDK_INT >= 21) {
outlineProvider = new OvalOutline();
}
drawable = new FilterMenuDrawable(ctx, Color.WHITE, collapsedRadius);
menuBounds = new Rect();
circleAnimator = ObjectAnimator.ofFloat(this, "expandProgress", 0, 0);
circleAnimator.setInterpolator(new OvershootInterpolator());
circleAnimator.setDuration(DURATION);

colorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), primaryColor, primaryDarkColor);
colorAnimator.setDuration(DURATION);
colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
primaryDarkPaint.setColor((Integer) animation.getAnimatedValue());
}
});
setSoundEffectsEnabled(true);
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() > 0) {
throw new IllegalStateException("should not add any child view to FilterMenuLayout ");
}
}

public float getExpandProgress() {
return expandProgress;
}

public void setExpandProgress(float progress) {
this.expandProgress = progress;
primaryPaint.setAlpha(Math.min(255, (int) (progress * 255)));

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
invalidateOutline();
}
drawable.setExpandProgress(progress);
invalidate();
}

@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}

void collapse(boolean animate) {
state = STATE_COLLAPSE;
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).setVisibility(View.GONE);
}
invalidate();
if (animate) {
startCollapseAnimation();
}
if (menu != null && menu.getListener() != null) {
menu.getListener().onMenuCollapse();
}
}

void expand(boolean animate) {
state = STATE_EXPAND;
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).setVisibility(View.VISIBLE);
}
invalidate();
if (animate) {
startExpandAnimation();
} else {
setItemsAlpha(1f);
}
if (menu != null && menu.getListener() != null) {
menu.getListener().onMenuExpand();
}
}

void toggle(boolean animate) {
if (state == STATE_COLLAPSE) {
expand(animate);
} else if (state == STATE_EXPAND) {
collapse(animate);
}
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);


setMeasuredDimension(width, height);
measureChildren(widthMeasureSpec, heightMeasureSpec);

}

@Override
protected boolean verifyDrawable(Drawable who) {
return who == drawable || super.verifyDrawable(who);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

if (getChildCount() == 0) {
return;
}
calculateMenuItemPosition();
for (int i = 0; i < getChildCount(); i++) {
FilterMenu.Item item = (FilterMenu.Item) getChildAt(i).getTag();
item.setBounds(
l + item.getX(),
t + item.getY(),
l + item.getX() + item.getView().getMeasuredWidth(),
t + item.getY() + item.getView().getMeasuredHeight()
);
Rect bounds = item.getBounds();
item.getView().layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
}

}

@Override
public boolean onTouchEvent(MotionEvent event) {
touchPoint.set((int) event.getX(), (int) event.getY());
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
isExpand = false;
double distance = pointsDistance(touchPoint, center);
if (distance > (collapsedRadius + (expandedRadius - collapsedRadius) * expandProgress)) {
if (state == STATE_EXPAND) {
collapse(true);
return true;
}
return false;
} else {
if (state == STATE_COLLAPSE) {
expand(true);
isExpand = true;
}
return true;
}
}
case MotionEvent.ACTION_MOVE: {
if (inChild) {
if (!inArea(touchPoint, touchedItem.getBounds(), .2f)) {
touchedItem.getView().setPressed(false);
inChild = false;
}
} else {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
FilterMenu.Item item = (FilterMenu.Item) getChildAt(i).getTag();
if (inArea(touchPoint, item.getBounds(), .2f)) {
touchedItem = item;
inChild = true;
child.dispatchTouchEvent(event);
child.setPressed(true);
break;
}
}
}
break;
}
case MotionEvent.ACTION_UP: {
if (inChild) {
if (menu != null) {
if (menu.getListener() != null) {
collapse(true);
menu.getListener().onMenuItemClick(touchedItem.getView(), touchedItem.getPosition());
}
}
touchedItem.getView().setPressed(false);
inChild = false;
}
if (!isExpand) {
collapse(true);
return true;
}
double distance = pointsDistance(touchPoint, center);
if (distance > (collapsedRadius + (expandedRadius - collapsedRadius) * expandProgress)) {
collapse(true);
return true;
}
break;
}
}
return super.onTouchEvent(event);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//        for (int i = 0; i < getChildCount(); i++) {
//            View child = getChildAt(i);
//            FilterMenu.Item item = (FilterMenu.Item) getChildAt(i).getTag();
//            if(inArea(touchPoint, item.getBounds())){
//                return false;
//            }
//        }
return super.onInterceptTouchEvent(ev);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.d(TAG, "onSizeChanged: " + w + ", " + h);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setOutlineProvider(outlineProvider);
}
int x, y;
if (centerHorizontal) {
x = w / 2 + centerLeft - centerRight;
} else {
x = centerLeft != 0 ? centerLeft : w - centerRight;
}
if (centerVertical) {
y = h / 2 + centerTop - centerBottom;
} else {
y = centerTop != 0 ? centerTop : h - centerBottom;
}
center.set(x, y);

int left = Math.max(getPaddingLeft(), center.x - expandedRadius);
int top = Math.max(getPaddingTop(), center.y - expandedRadius);
int right = Math.min(w - getPaddingRight(), center.x + expandedRadius);
int bottom = Math.min(h - getPaddingBottom(), center.y + expandedRadius);

menuBounds.set(left, top, right, bottom);

calculateIntersectPoints();
drawable.setBounds(center.x - drawable.getIntrinsicWidth() / 2,
center.y - drawable.getIntrinsicHeight() / 2,
center.x + drawable.getIntrinsicWidth() / 2,
center.y + drawable.getIntrinsicHeight() / 2
);

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (expandProgress > 0f) {
canvas.drawCircle(center.x, center.y, collapsedRadius + (expandedRadius - collapsedRadius) * expandProgress, primaryPaint);
}
canvas.drawCircle(center.x, center.y, collapsedRadius + (collapsedRadius * .2f * expandProgress), primaryDarkPaint);
drawable.draw(canvas);
}

void startExpandAnimation() {
//animate circle
circleAnimator.setFloatValues(getExpandProgress(), 1f);
circleAnimator.start();

//animate color
colorAnimator.setObjectValues(colorAnimator.getAnimatedValue() == null ? primaryColor : colorAnimator.getAnimatedValue(), primaryDarkColor);
colorAnimator.start();
//animate menu item
int delay = DURATION_BETWEEN_ITEM;
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).animate()
.setStartDelay(delay)
.setDuration(DURATION)
.alphaBy(0f)
.scaleXBy(0.5f)
.scaleX(1f)
.scaleYBy(0.5f)
.scaleY(1.f)
.alpha(1f)
.start();
delay += DURATION_BETWEEN_ITEM;
}
}

void startCollapseAnimation() {
//animate circle
circleAnimator.setFloatValues(getExpandProgress(), 0f);
circleAnimator.start();

//animate color
colorAnimator.setObjectValues(colorAnimator.getAnimatedValue() == null ? primaryDarkColor : colorAnimator.getAnimatedValue(), primaryColor);
colorAnimator.start();

//animate menu item
int delay = DURATION_BETWEEN_ITEM;
for (int i = getChildCount() - 1; i >= 0; i--) {
getChildAt(i).animate()
.setStartDelay(delay)
.setDuration(DURATION)
.alpha(0)
.scaleX(0)
.scaleY(0)
.start();
delay += DURATION_BETWEEN_ITEM;
}

}

void setItemsAlpha(float alpha) {
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).setAlpha(alpha);
}
}

/**
* calculate and set position to menu items
*/
private void calculateMenuItemPosition() {

float itemRadius = (expandedRadius + collapsedRadius) / 2, f;
RectF area = new RectF(
center.x - itemRadius,
center.y - itemRadius,
center.x + itemRadius,
center.y + itemRadius);
Path path = new Path();
path.addArc(area, (float) fromAngle, (float) (toAngle - fromAngle));
PathMeasure measure = new PathMeasure(path, false);
float len = measure.getLength();
int divisor = getChildCount();
float divider = len / divisor;

for (int i = 0; i < getChildCount(); i++) {
float[] coords = new float[2];
measure.getPosTan(i * divider + divider * .5f, coords, null);
FilterMenu.Item item = (FilterMenu.Item) getChildAt(i).getTag();
item.setX((int) coords[0] - item.getView().getMeasuredWidth() / 2);
item.setY((int) coords[1] - item.getView().getMeasuredHeight() / 2);
}
}

/**
* find all intersect points, and calculate menu items display area;
*/
private void calculateIntersectPoints() {
intersectPoints.clear();

/** order intersect points clockwise **/
//left edge
if (center.x - menuBounds.left < expandedRadius) {
int dy = (int) Math.sqrt(Math.pow(expandedRadius, 2) - Math.pow(center.x - menuBounds.left, 2));
if (center.y - dy > menuBounds.top) {
intersectPoints.add(new Point(menuBounds.left, center.y - dy));
}

if (center.y + dy < menuBounds.bottom) {
intersectPoints.add(new Point(menuBounds.left, center.y + dy));
}

}
//top edge
if (center.y - menuBounds.top < expandedRadius) {
int dx = (int) Math.sqrt(Math.pow(expandedRadius, 2) - Math.pow(center.y - menuBounds.top, 2));
if (center.x + dx < menuBounds.right) {
intersectPoints.add(new Point(center.x + dx, menuBounds.top));
}
if (center.x - dx > menuBounds.left) {
intersectPoints.add(new Point(center.x - dx, menuBounds.top));
}
}

//right edge
if (menuBounds.right - center.x < expandedRadius) {
int dy = (int) Math.sqrt(Math.pow(expandedRadius, 2) - Math.pow(menuBounds.right - center.x, 2));

if (center.y - dy > menuBounds.top) {
intersectPoints.add(new Point(menuBounds.right, center.y - dy));
}
if (center.y + dy < menuBounds.bottom) {
intersectPoints.add(new Point(menuBounds.right, center.y + dy));
}

}
//bottom edge
if (menuBounds.bottom - center.y < expandedRadius) {
int dx = (int) Math.sqrt(Math.pow(expandedRadius, 2) - Math.pow(menuBounds.bottom - center.y, 2));
if (center.x + dx < menuBounds.right) {
intersectPoints.add(new Point(center.x + dx, menuBounds.bottom));
}
if (center.x - dx > menuBounds.left) {
intersectPoints.add(new Point(center.x - dx, menuBounds.bottom));
}
}


//find the maximum arc in menuBounds
int size = intersectPoints.size();
if (size == 0) {
fromAngle = 0;
toAngle = 360;
return;
}
int indexA = size - 1;
double maxAngle = arcAngle(center, intersectPoints.get(0), intersectPoints.get(indexA), menuBounds, expandedRadius);
for (int i = 0; i < size - 1; i++) {
Point a = intersectPoints.get(i);
Point b = intersectPoints.get(i + 1);
double angle = arcAngle(center, a, b, menuBounds, expandedRadius);
Point midnormalPoint = findMidnormalPoint(center, a, b, menuBounds, expandedRadius);

//if the arc(a->midnormalPoint->b) is in menuBounds and the angle is bigger, select it
int pointerIndex = i;
int endIndex = indexA + 1;
if (!isClockwise(center, a, midnormalPoint)) {
int tmpIndex = pointerIndex;
pointerIndex = endIndex;
endIndex = tmpIndex;
}
if (pointerIndex == intersectPoints.size() - 1) {
pointerIndex = 0;
} else {
pointerIndex++;
}

if (pointerIndex == endIndex && angle > maxAngle) {
indexA = i;
maxAngle = angle;
}
}

Point a = intersectPoints.get(indexA);
Point b = intersectPoints.get(indexA + 1 >= size ? 0 : indexA + 1);
Point midnormalPoint = findMidnormalPoint(center, a, b, menuBounds, expandedRadius);

Point x = new Point(menuBounds.right, center.y);
if (!isClockwise(center, a, midnormalPoint)) {
Point tmp = a;
a = b;
b = tmp;
}

fromAngle = pointAngleOnCircle(center, a, x);
toAngle = pointAngleOnCircle(center, b, x);
toAngle = toAngle <= fromAngle ? 360 + toAngle : toAngle;
}

/**
* judge a->b is ordered clockwise
*
* @param center
* @param a
* @param b
* @return
*/
private boolean isClockwise(Point center, Point a, Point b) {
double cross = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y);
return cross > 0;
}

public int getState() {
return state;
}

public void setMenu(FilterMenu menu) {
this.menu = menu;
}


@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
this.setExpandProgress(ss.expandProgress);
this.setPrimaryColor(ss.primaryColor);
this.setPrimaryDarkColor(ss.primaryDarkColor);
this.setCollapsedRadius(ss.collapsedRadius);
this.setExpandedRadius(ss.expandedRadius);
if (ss.state == STATE_COLLAPSE) {
collapse(false);
} else {
expand(false);
}
}

@Override
protected Parcelable onSaveInstanceState() {
SavedState ss = new SavedState(super.onSaveInstanceState());
ss.expandProgress = getExpandProgress();
ss.primaryColor = getPrimaryColor();
ss.primaryDarkColor = getPrimaryDarkColor();
ss.collapsedRadius = getCollapsedRadius();
ss.expandedRadius = getExpandedRadius();
ss.state = getState();

return ss;
}


public int getExpandedRadius() {
return expandedRadius;
}

public void setExpandedRadius(int expandedRadius) {
this.expandedRadius = expandedRadius;
requestLayout();
}

public int getCollapsedRadius() {
return collapsedRadius;
}

public void setCollapsedRadius(int collapsedRadius) {
this.collapsedRadius = collapsedRadius;
requestLayout();
}

public int getPrimaryColor() {
return primaryColor;
}

public void setPrimaryColor(int color) {
this.primaryColor = color;
primaryPaint.setColor(primaryColor);
invalidate();
}

public int getPrimaryDarkColor() {
return primaryDarkColor;
}

public void setPrimaryDarkColor(int color) {
this.primaryDarkColor = color;
primaryDarkPaint.setColor(color);
invalidate();
}

static class SavedState extends BaseSavedState {

public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel source) {
return new SavedState(source);
}

public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
public float expandProgress;
public int primaryColor;
public int primaryDarkColor;
public int collapsedRadius;
public int expandedRadius;
public int state;

public SavedState(Parcelable superState) {
super(superState);
}

private SavedState(Parcel in) {
super(in);
this.expandProgress = in.readFloat();
this.primaryColor = in.readInt();
this.primaryDarkColor = in.readInt();
this.collapsedRadius = in.readInt();
this.expandedRadius = in.readInt();
this.state = in.readInt();
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(this.expandProgress);
dest.writeInt(this.primaryColor);
dest.writeInt(this.primaryDarkColor);
dest.writeInt(this.collapsedRadius);
dest.writeInt(this.expandedRadius);
dest.writeInt(this.state);
}

}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class OvalOutline extends ViewOutlineProvider {
public OvalOutline() {
super();
}

@Override
public void getOutline(View view, Outline outline) {
int radius = (int) (collapsedRadius + (expandedRadius - collapsedRadius) * expandProgress);
Rect area = new Rect(
center.x - radius,
center.y - radius,
center.x + radius,
center.y + radius);
outline.setRoundRect(area, radius);
}
}


}

IMenu.java


package com.linroid.filtermenu.library;

/**
* Created by linroid on 15/3/9.
*/
public interface IMenu {
void collapse(boolean animate);
void expand(boolean animate);
void toggle(boolean animate);
void setMenuLayout(FilterMenuLayout layout);
}

 

———————————————————————————————————————————————————————-

 

Create New Project FilterMenu And Link Above project As Lib Project.

 

—>  AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.linroid.filtermenu" >
<uses-permission android:name="android.permission.VIBRATE"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_action_add"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.linroid.filtermenu.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

In Values Folder

 

styles.xml


<resources>

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>

</resources>

strings.xml

 


<resources>
<string name="app_name">FilterMenuDemo</string>

<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="action_github">Github</string>
</resources>

 

dimens.xml

 


<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

In Layout Folder

activity_main.xml


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.linroid.filtermenu.library.FilterMenuLayout
android:id="@+id/filter_menu1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="8dp"
app:fm_expandedRadius="96dp"
app:fm_collapsedRadius="24dp"
app:fm_centerHorizontal="true"
app:fm_centerVertical="true"
app:fm_centerLeft="30dp"
app:fm_primaryColor="#ff636363"
app:fm_primaryDarkColor="#ff292929">
</com.linroid.filtermenu.library.FilterMenuLayout>

<com.linroid.filtermenu.library.FilterMenuLayout
android:id="@+id/filter_menu2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="8dp"
app:fm_expandedRadius="96dp"
app:fm_collapsedRadius="24dp"
app:fm_centerTop="100dp"
app:fm_centerLeft="48dp"
app:fm_primaryColor="#ff3163"
app:fm_primaryDarkColor="#ce3d68">
</com.linroid.filtermenu.library.FilterMenuLayout>

<com.linroid.filtermenu.library.FilterMenuLayout
android:id="@+id/filter_menu3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="8dp"
app:fm_expandedRadius="96dp"
app:fm_collapsedRadius="24dp"
app:fm_centerBottom="50dp"
app:fm_centerRight="50dp"
app:fm_primaryColor="#ff37aa4a"
app:fm_primaryDarkColor="#ff20622b">
</com.linroid.filtermenu.library.FilterMenuLayout>

<com.linroid.filtermenu.library.FilterMenuLayout
android:id="@+id/filter_menu4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="8dp"
app:fm_expandedRadius="96dp"
app:fm_collapsedRadius="24dp"
app:fm_centerBottom="50dp"
app:fm_centerLeft="50dp"
app:fm_primaryColor="#ff5791aa"
app:fm_primaryDarkColor="#ff334862">
</com.linroid.filtermenu.library.FilterMenuLayout>
</FrameLayout>

Add Images in Drawable Folder.

ic_action_location_2 ic_action_io ic_action_info ic_action_clock ic_action_add

 

 

In Java File.

 

 

MainActivity.java

 


package com.linroid.filtermenu;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import com.linroid.filtermenu.library.FilterMenu;
import com.linroid.filtermenu.library.FilterMenuLayout;


public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FilterMenuLayout layout1 = (FilterMenuLayout) findViewById(R.id.filter_menu1);
attachMenu1(layout1);

FilterMenuLayout layout2 = (FilterMenuLayout) findViewById(R.id.filter_menu2);
attachMenu2(layout2);

FilterMenuLayout layout3 = (FilterMenuLayout) findViewById(R.id.filter_menu3);
attachMenu3(layout3);

FilterMenuLayout layout4 = (FilterMenuLayout) findViewById(R.id.filter_menu4);
attachMenu4(layout4);
}
private FilterMenu attachMenu1(FilterMenuLayout layout){
return new FilterMenu.Builder(this)
.addItem(R.drawable.ic_action_add)
.addItem(R.drawable.ic_action_clock)
.addItem(R.drawable.ic_action_info)
.addItem(R.drawable.ic_action_io)
.addItem(R.drawable.ic_action_location_2)
.attach(layout)
.withListener(listener)
.build();
}
private FilterMenu attachMenu2(FilterMenuLayout layout){
return new FilterMenu.Builder(this)
.addItem(R.drawable.ic_action_add)
.addItem(R.drawable.ic_action_clock)
.addItem(R.drawable.ic_action_info)
.addItem(R.drawable.ic_action_location_2)
.attach(layout)
.withListener(listener)
.build();
}
private FilterMenu attachMenu3(FilterMenuLayout layout){
return new FilterMenu.Builder(this)
.addItem(R.drawable.ic_action_add)
.addItem(R.drawable.ic_action_clock)
.addItem(R.drawable.ic_action_location_2)
.attach(layout)
.withListener(listener)
.build();
}
private FilterMenu attachMenu4(FilterMenuLayout layout){
return new FilterMenu.Builder(this)
.inflate(R.menu.menu_filter)
.attach(layout)
.withListener(listener)
.build();
}



FilterMenu.OnMenuChangeListener listener = new FilterMenu.OnMenuChangeListener() {

@Override
public void onMenuItemClick(View view, int position) {
Toast.makeText(MainActivity.this, "Touched position " + position, Toast.LENGTH_SHORT).show();
}

@Override
public void onMenuCollapse() {

}


@Override
public void onMenuExpand() {

}
};
}

——————>>>>>>>>>>> Run Project ————————>>>>>>>>>>>>

admin@androidtrainee

 Previous Article Android Textfield For Material Design (Android L).
Next Article   Universal Image Loader With GridView For Android.

Related Posts

  • Android New Quick Action Animation.

    July 15, 2015
  • Android satellite menu Animation.

    July 15, 2015
  • Android Staggered Grid & List View.

    July 14, 2015

Leave a Reply

Cancel reply

Tags

admob Advertising Networks AerServ Airpush android android ads android Advertising Networks Android App android chart animation Android GridView android L android lollipop androidmapv2 AppBrain AppFlood Appia AppKey Appnext AppOptim Appwiz chart chartview Epom Market google place api GridView Image Loader InMobi LeadBolt location map mapv2 mapv2 api material design Minimob Mobicow MobileCore MobiMicro NativeX Pingjam RevMob StarApplication startapp TapContext touched location Widdit

Count per Day

  • 347Reads yesterday:
  • 463843Total visitors:
  • 63Visitors today:
  • 2436Visitors per month:
  • 0Visitors currently online:
© Copyright 2014. Theme by BloomPixel.
Posting....