这次分析一个扇形菜单展开的自定义View, 也是我实习期间做的一个印象比较深刻的自定义View, 前后切换了很多种实现思路, 先看看效果展示
效果展示
效果分析
实现分析
使用adapter适配器去设置View, 用户可自定义性强, 不过每次使用需要去设置Adapter, 较为繁琐
直接调用ItemView, 将ImageView和TextView写死, 用户操作简单, 但是缺乏可定制性(利他)
本次功能实现采用了方案 2
实现步骤
使用方式
BottomSectorMenuView.Converter(mFab) .setToggleDuration(500, 800) .setAnchorRotationAngle(135f) .addMenuItem(R.drawable.icon_camera, "拍照") { Toast.makeText(this@MainActivity, "拍照", Toast.LENGTH_SHORT).show() } .addMenuItem(R.drawable.icon_photo, "图片") { Toast.makeText(this@MainActivity, "图片", Toast.LENGTH_SHORT).show() } .addMenuItem(R.drawable.icon_text, "文字") { Toast.makeText(this@MainActivity, "文字", Toast.LENGTH_SHORT).show() } .addMenuItem(R.drawable.icon_video, "视频") { Toast.makeText(this@MainActivity, "视频", Toast.LENGTH_SHORT).show() } .addMenuItem(R.drawable.icon_camera_shooting, "摄像") { Toast.makeText(this@MainActivity, "摄像", Toast.LENGTH_SHORT).show() } .apply()
源码实现
/** * Email: frankchoochina@gmail.com * Created by FrankChoo on 2017/10/9. * Description: 底部扇形菜单, 通过Adapter添加Item * 1. 调用openMenu打开菜单 * 2. 调用closeMenu关闭菜单 */ public class SectorMenuView extends FrameLayout { // 每个ItemView之间的角度差 private double mAngle; // 圆心坐标 private Point mCenterPoint; // ItemView到圆心的半径 private float mMaxItemRadius; private float mCurItemRadius; // 背景圆的半径 private float mMaxBkgRadius; private float mCurBkgRadius; private Paint mPaint; private SectorMenuAdapter mAdapter; private OnMenuOpenedListener mMenuOpenedListener; private OnMenuClosedListener mMenuClosedListener; public SectorMenuView(Context context) { this(context, null); } public SectorMenuView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SectorMenuView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { // 初始化画笔 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setDither(true); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(Color.WHITE); // 设置背景圆绘制的半径 int displayWidth = getResources().getDisplayMetrics().widthPixels; mMaxBkgRadius = (int) Math.sqrt(Math.pow(displayWidth/2, 2.0) + Math.pow(displayWidth/2, 2.0)); // 开启ViewGroup的绘制 setWillNotDraw(false); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 这里直接将宽高写死, 不支持Margin int width = getResources().getDisplayMetrics().widthPixels; int height = (int) Math.sqrt(Math.pow(width / 2, 2.0) + Math.pow(width / 2, 2.0)); setMeasuredDimension(width, height); // 计算半径 int realWidth = width - getPaddingRight() - getPaddingLeft(); int realHeight = height - getPaddingTop() - getPaddingBottom(); mMaxItemRadius = realWidth / 2; // 计算圆心 int centerX = getPaddingLeft() + realWidth / 2; int centerY = getPaddingTop() + realHeight; mCenterPoint = new Point(centerX, centerY); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); double curAngle = Math.PI - mAngle * (i + 1); int childCenterX = (int) (mCenterPoint.x + mCurItemRadius * Math.cos(curAngle)); int childCenterY = (int) (mCenterPoint.y - mCurItemRadius * Math.sin(curAngle)); child.layout( childCenterX - child.getMeasuredWidth() / 2, childCenterY - child.getMeasuredHeight() / 2, childCenterX + child.getMeasuredWidth() / 2, childCenterY + child.getMeasuredHeight() / 2 ); // 这里动态的去设置子View的透明度 child.setAlpha(mCurItemRadius / mMaxItemRadius); } } @Override protected void onDraw(Canvas canvas) { canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mCurBkgRadius, mPaint); super.onDraw(canvas); } public void setAdapter(SectorMenuAdapter adapter) { mAdapter = adapter; for (int i = 0; i < mAdapter.getCount(); i++) { View child = mAdapter.getView(i, null, this); addView(child); } mAngle = Math.PI / (mAdapter.getCount() + 1); } public void setBackgroudColor(@ColorInt int color) { mPaint.setColor(color); } public void setBackgroundResource(@ColorRes int colorResId) { mPaint.setColor(ContextCompat.getColor(getContext(), colorResId)); } /** * 打开菜单 */ public void openMenu() { if (mMaxItemRadius == 0) { mMaxItemRadius = getResources().getDisplayMetrics().widthPixels / 2 - getPaddingRight() - getPaddingLeft(); } // 背景动画 ValueAnimator bkgAnim = ValueAnimator.ofFloat(0f, mMaxBkgRadius).setDuration(300); bkgAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurBkgRadius = (float) animation.getAnimatedValue(); invalidate(); } }); // item的位置动画 ValueAnimator itemTranslationAnim = ValueAnimator.ofFloat(0f, mMaxItemRadius).setDuration(300); itemTranslationAnim.setInterpolator(new OvershootInterpolator(2f)); itemTranslationAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurItemRadius = (float) animation.getAnimatedValue(); requestLayout(); } }); // 动画集合 final AnimatorSet set = new AnimatorSet(); set.playSequentially(bkgAnim, itemTranslationAnim); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { setAlpha(1f); setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animator animation) { if (mMenuOpenedListener != null) { mMenuOpenedListener.opened(); } } }); set.start(); } /** * 关闭菜单 */ public void closeMenu() { // Item动画 ValueAnimator itemViewAnim = ValueAnimator.ofFloat(mMaxItemRadius, 0f).setDuration(300); itemViewAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurItemRadius = (float) animation.getAnimatedValue(); requestLayout(); } }); itemViewAnim.setInterpolator(new AnticipateInterpolator(2f)); // 背景动画 ValueAnimator backgroundAnim = ValueAnimator.ofFloat(mMaxBkgRadius, 0f).setDuration(300); backgroundAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurBkgRadius = (float) animation.getAnimatedValue(); invalidate(); } }); // 这里设置了该View整体透明度的变化, 防止消失的背景不在锚点处, 显示效果突兀 ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f).setDuration(250); // 动画集合 AnimatorSet animatorSet = new AnimatorSet(); animatorSet.play(itemViewAnim).before(backgroundAnim); animatorSet.play(backgroundAnim).with(alphaAnim); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mMenuClosedListener != null) { mMenuClosedListener.closed(); } setVisibility(View.INVISIBLE); } }); animatorSet.start(); } public void setOnMenuOpenedListener(OnMenuOpenedListener listener) { mMenuOpenedListener = listener; } public void setOnMenuClosedListener(OnMenuClosedListener listener) { mMenuClosedListener = listener; } /** * 供外界调用的Adapter */ public abstract static class SectorMenuAdapter extends BaseAdapter { @Override public long getItemId(int position) { return 0; } @Override public Object getItem(int position) { return null; } @Override public View getView(int position, View convertView, ViewGroup parent) { return createView(position, parent); } protected abstract View createView(int position, ViewGroup parent); @Override public abstract int getCount(); } public interface OnMenuOpenedListener { void opened(); } public interface OnMenuClosedListener { void closed(); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍Flutter实现底部菜单导航,包括了Flutter实现底部菜单导航的使用技巧和注意事项,需要的朋友参考一下 简介 现在我们的 APP 上面都会在屏幕下方有一排的按钮,点击不同的按钮可以进入不同的界面。就是说在界面的底部会有一排的按钮导航。可看下面的图示。 完成图示 程序工程目录 梳理下实现步骤 我们需要实现这个底部菜单导航,就需要有底部菜单的那一排图标按钮。图标按钮是固定在一个工具
我有一个关于底部导航栏的奇怪问题,虽然我花了大量时间在它上面,但我无法解决。当我以“推荐”的方式(从许多教程中)使用它时,它就是无法导航。 那么,我所说的“推荐”方式是什么意思呢:我有一个单独的acticity,其中有一个名为“MainActivity”的navHostFragment。这个主要活动有一个XML布局文件,我将底部导航栏放在其中。BottomNavigationBar还有一个XML布
本文向大家介绍Flutter实现底部导航,包括了Flutter实现底部导航的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了Flutter实现底部导航的具体代码,供大家参考,具体内容如下 BottomNavigationBar使用 底部导航栏 主文件 main.dart (注意导入文件路径) 底部包含三个导航按钮,分别对应三个界面: firstPage.dart secondPage.
我想在我的项目中包括底部导航菜单,但它不起作用。底部导航菜单中没有显示菜单图标。我使用的是Android Studio北极狐。我做了所有正确的步骤,但仍然面临问题。这是我的代码,请帮我解决这个问题。谢谢。 主要活动Xml res\菜单\底部导航菜单 build.gradle 主题。xml
本文向大家介绍Android底部菜单栏实现的实例代码,包括了Android底部菜单栏实现的实例代码的使用技巧和注意事项,需要的朋友参考一下 Android 使用RadioGroup 实现底部导航菜单栏。 一、主界面布局的实现: 先来张效果图: 介绍一下总体界面包括的内容:底部五个导航按钮,主界面包括一个FrameLayout用来放五个Fragment。点击底部按钮会对应跳转到指定的界面。 实现布
本文向大家介绍Flutter实现底部导航栏,包括了Flutter实现底部导航栏的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了Flutter实现底部导航栏的具体代码,供大家参考,具体内容如下 效果 实现 先将自动生成的main.dart里面的代码删除, 创建app.dart作为首页的页面文件 创建today.dart、kb.dart、playground.dart三个页面文件作为ta