3、在自定义的ViewGroup中获取这些属性
Arcmenu.java
/**
- @author zhy
*/
public class ArcMenu extends ViewGroup implements OnClickListener
{
private static final String TAG = “ArcMenu”;
/**
- 菜单的显示位置
*/
private Position mPosition = Position.LEFT_TOP;
/**
- 菜单显示的半径,默认100dp
*/
private int mRadius = 100;
/**
- 用户点击的按钮
*/
private View mButton;
/**
- 当前ArcMenu的状态
*/
private Status mCurrentStatus = Status.CLOSE;
/**
- 回调接口
*/
private OnMenuItemClickListener onMenuItemClickListener;
/**
-
状态的枚举类
-
@author zhy
*/
public enum Status
{
OPEN, CLOSE
}
/**
-
设置菜单现实的位置,四选1,默认右下
-
@author zhy
*/
public enum Position
{
LEFT_TOP, RIGHT_TOP, RIGHT_BOTTOM, LEFT_BOTTOM;
}
public interface OnMenuItemClickListener
{
void onClick(View view, int pos);
}
public ArcMenu(Context context)
{
this(context, null);
}
public ArcMenu(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
/**
-
初始化属性
-
@param context
-
@param attrs
-
@param defStyle
*/
public ArcMenu(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
// dp convert to px
mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
mRadius, getResources().getDisplayMetrics());
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.ArcMenu, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.ArcMenu_position:
int val = a.getInt(attr, 0);
switch (val)
{
case 0:
mPosition = Position.LEFT_TOP;
break;
case 1:
mPosition = Position.RIGHT_TOP;
break;
case 2:
mPosition = Position.RIGHT_BOTTOM;
break;
case 3:
mPosition = Position.LEFT_BOTTOM;
break;
}
break;
case R.styleable.ArcMenu_radius:
// dp convert to px
mRadius = a.getDimensionPixelSize(attr, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100f,
getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
}
4、计算子元素的大小:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int count = getChildCount();
for (int i = 0; i < count; i++)
{
// mesure child
getChildAt(i).measure(MeasureSpec.UNSPECIFIED,
MeasureSpec.UNSPECIFIED);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
5、确定子元素的位置:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
if (changed)
{
layoutButton();
int count = getChildCount();
/**
-
设置所有孩子的位置 例如(第一个为按钮): 左上时,从左到右 ] 第2个:mRadius(sin0 , cos0)
-
第3个:mRadius(sina ,cosa) 注:[a = Math.PI / 2 * (cCount - 1)]
-
第4个:mRadius(sin2a ,cos2a) 第5个:mRadius(sin3a , cos3a) …
*/
for (int i = 0; i < count - 1; i++)
{
View child = getChildAt(i + 1);
child.setVisibility(View.GONE);
int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2)
- i));
int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2)
- i));
// childview width
int cWidth = child.getMeasuredWidth();
// childview height
int cHeight = child.getMeasuredHeight();
// 右上,右下
if (mPosition == Position.LEFT_BOTTOM
|| mPosition == Position.RIGHT_BOTTOM)
{
ct = getMeasuredHeight() - cHeight - ct;
}
// 右上,右下
if (mPosition == Position.RIGHT_TOP
|| mPosition == Position.RIGHT_BOTTOM)
{
cl = getMeasuredWidth() - cWidth - cl;
}
Log.e(TAG, cl + " , " + ct);
child.layout(cl, ct, cl + cWidth, ct + cHeight);
}
}
}
首先在layoutButton中对按钮位置就行设置,以及初始化点击事件;然后从第二个子元素开始为菜单项,分别设置其位置,计算的原理就是上面我画的草图,可以再去仔细看看,动手在纸上画一画。
/**
- 第一个子元素为按钮,为按钮布局且初始化点击事件
*/
private void layoutButton()
{
View cButton = getChildAt(0);
cButton.setOnClickListener(this);
int l = 0;
int t = 0;
int width = cButton.getMeasuredWidth();
int height = cButton.getMeasuredHeight();
switch (mPosition)
{
case LEFT_TOP:
l = 0;
t = 0;
break;
case LEFT_BOTTOM:
l = 0;
t = getMeasuredHeight() - height;
break;
case RIGHT_TOP:
l = getMeasuredWidth() - width;
t = 0;
break;
case RIGHT_BOTTOM:
l = getMeasuredWidth() - width;
t = getMeasuredHeight() - height;
break;
}
Log.e(TAG, l + " , " + t + " , " + (l + width) + " , " + (t + height));
cButton.layout(l, t, l + width, t + height);
}
这是定位Button的代码,此时的代码已经实现了定位,如果你把onLayout中childView.setVisibility(VISIBLE)。ArcMenu的整个控件的样子已经实现了,接下来就是点击事件,已经效果动画的实现了。
6、设置按钮点击事件
/**
- 为按钮添加点击事件
*/
@Override
public void onClick(View v)
{
mButton = findViewById(R.id.id_button);
if (mButton == null)
{
mButton = getChildAt(0);
}
rotateView(mButton, 0f, 270f, 300);
toggleMenu(300);
}
/**
-
按钮的旋转动画
-
@param view
-
@param fromDegrees
-
@param toDegrees
-
@param durationMillis
*/
public static void rotateView(View view, float fromDegrees,
float toDegrees, int durationMillis)
{
RotateAnimation rotate = new RotateAnimation(fromDegrees, toDegrees,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
rotate.setDuration(durationMillis);
rotate.setFillAfter(true);
view.startAnimation(rotate);
}
public void toggleMenu(int durationMillis)
{
int count = getChildCount();
for (int i = 0; i < count - 1; i++)
{
final View childView = getChildAt(i + 1);
childView.setVisibility(View.VISIBLE);
int xflag = 1;
int yflag = 1;
if (mPosition == Position.LEFT_TOP
|| mPosition == Position.LEFT_BOTTOM)
xflag = -1;
if (mPosition == Position.LEFT_TOP
|| mPosition == Position.RIGHT_TOP)
yflag = -1;
// child left
int cl = (int) (mRadius * Math.sin(Math.PI / 2 / (count - 2) * i));
// child top
int ct = (int) (mRadius * Math.cos(Math.PI / 2 / (count - 2) * i));
AnimationSet animset = new AnimationSet(true);
Animation animation = null;
if (mCurrentStatus == Status.CLOSE)
{// to open
animset.setInterpolator(new OvershootInterpolator(2F));
animation = new TranslateAnimation(xflag * cl, 0, yflag * ct, 0);
childView.setClickable(true);
childView.setFocusable(true);
} else
{// to close
animation = new TranslateAnimation(0f, xflag * cl, 0f, yflag
- ct);
childView.setClickable(false);
childView.setFocusable(false);
}
animation.setAnimationListener(new AnimationListener()
{
public void onAnimationStart(Animation animation)
{
}
public void onAnimationRepeat(Animation animation)
{
}
public void onAnimationEnd(Animation animation)
{
if (mCurrentStatus == Status.CLOSE)
childView.setVisibility(View.GONE);
}
});
animation.setFillAfter(true);
animation.setDuration(durationMillis);
// 为动画设置一个开始延迟时间,纯属好看,可以不设
animation.setStartOffset((i * 100) / (count - 1));
RotateAnimation rotate = new RotateAnimation(0, 720,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(durationMillis);
rotate.setFillAfter(true);
animset.addAnimation(rotate);
animset.addAnimation(animation);
childView.startAnimation(animset);
final int index = i + 1;
childView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
if (onMenuItemClickListener != null)
onMenuItemClickListener.onClick(childView, index - 1);
menuItemAnin(index - 1);
changeStatus();
}
});
}
changeStatus();
Log.e(TAG, mCurrentStatus.name() +“”);
}
点击时,触发TanslateAnimation动画,从定点向外扩展,也给点击按钮添加了一个旋转动画,每个子菜单项同样添加了旋转动画,且如果用户设置回调,调用回调接口;设置子菜单的点击事件。整体就是点击然后动画效果~~
7、设置子菜单的点击事件
/**
-
开始菜单动画,点击的MenuItem放大消失,其他的缩小消失
-
@param item
*/
private void menuItemAnin(int item)
{
for (int i = 0; i < getChildCount() - 1; i++)
{
View childView = getChildAt(i + 1);
if (i == item)
{
childView.startAnimation(scaleBigAnim(300));
} else
{
childView.startAnimation(scaleSmallAnim(300));
}
childView.setClickable(false);
childView.setFocusable(false);
}
}
/**
-
缩小消失
-
@param durationMillis
-
@return
*/
private Animation scaleSmallAnim(int durationMillis)
{
Animation anim = new ScaleAnimation(1.0f, 0f, 1.0f, 0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
anim.setDuration(durationMillis);
anim.setFillAfter(true);
return anim;
}
/**
-
放大,透明度降低
-
@param durationMillis
-
@return
*/
private Animation scaleBigAnim(int durationMillis)
{
《设计思想解读开源框架》
第一章、 热修复设计
-
第一节、 AOT/JIT & dexopt 与 dex2oat
-
第二节、 热修复设计之 CLASS_ISPREVERIFIED 问题
-
第三节、热修复设计之热修复原理
-
第四节、Tinker 的集成与使用(自动补丁包生成)
第二章、 插件化框架设计
-
第一节、 Class 文件与 Dex 文件的结构解读
-
第二节、 Android 资源加载机制详解
-
第三节、 四大组件调用原理
-
第四节、 so 文件加载机制
-
第五节、 Android 系统服务实现原理
第三章、 组件化框架设计
-
第一节、阿里巴巴开源路由框——ARouter 原理分析
-
第二节、APT 编译时期自动生成代码&动态类加载
-
第三节、 Java SPI 机制
-
第四节、 AOP&IOC
-
第五节、 手写组件化架构
第四章、图片加载框架
-
第一节、图片加载框架选型
-
第二节、Glide 原理分析
-
第三节、手写图片加载框架实战
第五章、网络访问框架设计
-
第一节、网络通信必备基础
-
第二节、OkHttp 源码解读
-
第三节、Retrofit 源码解析
第六章、 RXJava 响应式编程框架设计
-
第一节、链式调用
-
第二节、 扩展的观察者模式
-
第三节、事件变换设计
-
第四节、Scheduler 线程控制
第七章、 IOC 架构设计
-
第一节、 依赖注入与控制反转
-
第二节、ButterKnife 原理上篇、中篇、下篇
-
第三节、Dagger 架构设计核心解密
第八章、 Android 架构组件 Jetpack
-
第一节、 LiveData 原理
-
第二节、 Navigation 如何解决 tabLayout 问题
-
第三节、 ViewModel 如何感知 View 生命周期及内核原理
-
第四节、 Room 架构方式方法
-
第五节、 dataBinding 为什么能够支持 MVVM
-
第六节、 WorkManager 内核揭秘
-
第七节、 Lifecycles 生命周期
本文包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
通信必备基础** -
第二节、OkHttp 源码解读
-
第三节、Retrofit 源码解析
[外链图片转存中…(img-sTBK5oav-1714573627544)]
第六章、 RXJava 响应式编程框架设计
-
第一节、链式调用
-
第二节、 扩展的观察者模式
-
第三节、事件变换设计
-
第四节、Scheduler 线程控制
[外链图片转存中…(img-NDa9XueG-1714573627545)]
第七章、 IOC 架构设计
-
第一节、 依赖注入与控制反转
-
第二节、ButterKnife 原理上篇、中篇、下篇
-
第三节、Dagger 架构设计核心解密
[外链图片转存中…(img-Ua6t4gv2-1714573627546)]
第八章、 Android 架构组件 Jetpack
-
第一节、 LiveData 原理
-
第二节、 Navigation 如何解决 tabLayout 问题
-
第三节、 ViewModel 如何感知 View 生命周期及内核原理
-
第四节、 Room 架构方式方法
-
第五节、 dataBinding 为什么能够支持 MVVM
-
第六节、 WorkManager 内核揭秘
-
第七节、 Lifecycles 生命周期
[外链图片转存中…(img-cvkhY3AQ-1714573627547)]
本文包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
[外链图片转存中…(img-PRzS5kNA-1714573627549)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!