zoukankan      html  css  js  c++  java
  • Android Custom View系列《圆形菜单一》

    前言

    自定义view能够做出很多不同寻常的效果,圆形菜单交互效果不错,目前网上有两个版本,虽然比较庞大,但非常值得研究与学习。

    radial-menu-widget: https://code.google.com/p/radial-menu-widget/

    Radial-Menu-Widget-Android:https://github.com/strider2023/Radial-Menu-Widget-Android

    这两个版本呢实际上第一个是最原始的作者Jason Valestin,后来被Arindam Nath修改后出现了后面的版本。在分析过程中可以逐个击破,关键在于理解要点,本文讲自定义一个圆形的view作为自定义圆形菜单的一个入门基础,暂且命名为CircleView,点击后变化颜色。

    源码链接: https://github.com/avenwu/RadialDemo/tree/draw_circle

    实现

    如果要自定义一个圆形的菜单,那么现有的LinearLayout或者RelativeLayout等都已经无法满足,我们需要从View直接继承,由于需要处理触摸事件,所以需要重载onTouchEvent(),来根据当前触摸的坐标额动作状态调整view

    ,除此之外需要判断点击的位置出于菜单的可是区域内,在Android中所有的view本质上是矩形区域的,所以需要通过数学计算来判断当前是否点中了菜单,现在我们对这两个关键做类似的实现。

    1.初始化画笔菜单位置及大小

    在合适的地方初始化资源是很重要的,对画笔和菜单半径等我们可以在构造方法内赋值,


    201401101751.jpg

    绘制一个圆还需要中心坐标,这个值我们在view的大小确定时初始化,不要尝试直接获取view的高宽,这两个值必须在view绘制后才拿的到,这里我们在onMeasure和onSizeChanged都可以得到view的大小。


    201401101752.jpg

    2.通过画布绘制菜单

    绘制一个圆形比较简单,直接调用canvas的drawCircle方法


    201401101754.jpg

    3.处理触摸事件

    重载onTouchEvent方法我们监听到view上面的触摸动作,一般来说ACTION_DOWN, ACTION_UP, ACTION_MOVE都是比较重要的

    我们在点击view的时候更改画笔的颜色为粉红色,当手离开时改回默认的红色,所以可以在ACTION_DOWN时设置color为Color.MAGENTA, 在ACTION_UP时设置为Color.RED

    另外在我们按住屏幕后移动位置,这是有可能会移除菜单区域,所以在ACTION_MOVE时我们也需要设置


    201401101800.jpg

    4.计算触摸位置是否在菜单内

    这一步将直接影响我们的触摸效果,这里我们的区域是圆形,所以比较好处理,只需计算触摸点到圆心的距离就可以知道相对位置。


    201401101803.jpg

    后面当单一圆形菜单分割为多个菜单项时,位置计算会复杂一些。

    完整代码:

    package com.avenwu.radialdemo;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.View;
    
    /**
     * @author chaobin
     * @date 1/10/14.
     */
    public class CircleView extends View {
        private float mCenterX;
        private float mCenterY;
        private float mRadius;
        private Paint mPaint;
    
        public CircleView(Context context) {
            this(context, null);
        }
    
        public CircleView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            mRadius = getContext().getResources().getDisplayMetrics().density * 100;
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setColor(Color.RED);
            mPaint.setStrokeWidth(getContext().getResources().getDisplayMetrics().density * 5);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int width = MeasureSpec.getSize(widthMeasureSpec) / 2;
            int height = MeasureSpec.getSize(heightMeasureSpec) / 2;
            Log.d("CircleView", "onMeasure, height=" + height + ", width=" + width);
            updateCenter(width, height);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            Log.d("CircleView", "onSizeChanged: w=" + w + ", h=" + h);
            updateCenter(w, h);
        }
    
        void updateCenter(int x, int y) {
            mCenterX = x / 2;
            mCenterY = y / 2;
        }
    
        boolean isInside(float x, float y) {
            return Math.pow(x - mCenterX, 2) + Math.pow(y - mCenterY, 2)
                    <= Math.pow(mCenterX - getPaddingLeft(), 2);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    if (isInside(event.getX(), event.getY())) {
                        mPaint.setColor(Color.MAGENTA);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    mPaint.setColor(Color.RED);
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (!isInside(event.getX(), event.getY())) {
                        mPaint.setColor(Color.RED);
                    }
                    Log.d("CircleView", "position, x=" + event.getX() + ", y=" + event.getY());
                    break;
            }
            invalidate();
            return true;
        }
    
    }
  • 相关阅读:
    自定义注解(注解扫描)
    Redis缓存淘汰策略
    粘包问题
    MySQL事务日志
    分布式事务
    https的工作流程
    CAP原则和BASE理论
    设计模式(一)
    限流的原理以及常用算法
    散列冲突(哈希碰撞)的解决办法
  • 原文地址:https://www.cnblogs.com/avenwu/p/3514178.html
Copyright © 2011-2022 走看看