博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android学习笔记之如何使用圆形菜单实现旋转效果...
阅读量:6893 次
发布时间:2019-06-27

本文共 12226 字,大约阅读时间需要 40 分钟。

PS:最近忙于项目的开发,一直都没有去写博客,是时候整理整理自己在其中学到的东西了...

 

学习内容:

1.使用圆形菜单并实现旋转效果..

    Android的圆形菜单我也是最近才接触到,由于在界面中确实是使用到了,因此就去学习了一下圆形菜单的使用,并且实现菜单的旋转效果,类似于摩天轮那样的效果,个人感觉还是蛮不错的,就是在实现的过程中有点麻烦...通过动态加载的方式,使用ViewGroup来实现了这个过程...个人感觉是一个蛮复杂的过程,最后附上源码...上面先附上效果图,其实这个图给人的感觉更像是摩天轮,转动中心位置的时候,几个附带的小圆会进行转动,通过转动我们可以看到被挡住的部分...算是为了实现一种互动吧...直接上实现过程的代码,然后在进行解释...估计看到下面的代码,很多人就恶心了...但是如果坚持看完了,那么就真正学会了...先不要看下面的代码,把代码先过掉...看下面的解释...

package com.circle.cil_212_app;import com.example.cil_212_app.R;import android.annotation.SuppressLint;import android.annotation.TargetApi;import android.content.Context;import android.os.Build;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.TextView;import android.widget.Toast;@TargetApi(Build.VERSION_CODES.CUPCAKE)public class CircleMenuLayout extends ViewGroup{    //→_→ 第一部分...        private int mRadius;   //定义layout的半径...        //定义了两个数值...在选取半径长度的时候需要使用...    private float mMaxChildDimesionRadio = 1/ 4f;    private float mCenterItemDimesionRadio = 1/ 3f;        private LayoutInflater inflater;  //自定义加载布局...        private double StartAngle;     //定义初始角度...        private String[] ItemTexts = new String[]{"HTML", "CSS", "JS",            "JQuery", "DOM", "TEMPLETE"};    private int[] ItemImgs = new int[]{R.drawable.home_mbank_1_normal,R.drawable.home_mbank_2_normal,R.drawable.home_mbank_3_normal,R.drawable.home_mbank_4_normal,R.drawable.home_mbank_5_normal,R.drawable.home_mbank_6_normal};        private int TouchSlop;        /*     * 加速度检测     * */        private float DownAngle;    private float TmpAngle;    private long DownTime;    private boolean isFling;  //判断是否在自由旋转...        //→_→  第二部分..            public CircleMenuLayout(Context context, AttributeSet attrs) {        super(context,attrs);        // TODO Auto-generated constructor stub                inflater=LayoutInflater.from(context);              for(int i=0;i
230 && !isFling){ post(mFlingRunnable = new FlingRunnable(anglePrMillionSecond)); } if(Math.abs(anglePrMillionSecond) >230 || isFling){ return true ; } break; } return super.dispatchTouchEvent(event); } private float getAngle(float xTouch, float yTouch){ double x = xTouch - (mRadius / 2d); double y = yTouch - (mRadius / 2d); return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI); } private int getQuadrant(float x, float y){ int tmpX = (int) (x - mRadius / 2); int tmpY = (int) (y - mRadius / 2); if (tmpX >= 0){ return tmpY >= 0 ? 4 : 1; }else{ return tmpY >= 0 ? 3 : 2; } } //→_→ 第六部分 private class FlingRunnable implements Runnable{ private float velocity; public FlingRunnable(float velocity){ this.velocity = velocity; } public void run(){ if ((int) Math.abs(velocity) < 20){ isFling = false; return; } isFling = true; StartAngle += (velocity / 30); velocity /= 1.0666F; postDelayed(this, 30); requestLayout(); } }}

  在这里开始进行正式的解释,我把代码分成了六个部分...这六个部分最核心的地方就属于第三部分和第四部分和第五部分了,因此我就先解释三四五部分...首先是第三个部分,第三个部分是测量部分,因为这个布局中,这七个彩色圆圈图需要我们动态的添加到我们的布局上,为什么要动态布局,而不是静态,因为后面我们需要实现旋转效果,可能有人不明白为什么旋转就要用动态加载布局,其实旋转就是对布局的样式进行持续刷新,每一次刷新都会导致控件的位置的改变,我们总不能把旋转一圈的布局文件全写完吧?因此动态的添加是为了有更高的灵活性..因此我们需要一个测量的过程,因为动态的加载控件时,系统需要对每一个控件的位置进行计算,找到一个合适的ViewGroup(ViewGroup我们可以把它理解成一个容器,这个容器可以放入组件,也可以放入子容器),然后将控件放入到其中,但是系统计算的数据往往不一定是我们想要的那样...因此我在这里使用了第三部分进行计算...计算每一个控件需要多大的空间才能够放得下...这就是第三部分的完整解释...

//→_→ 第三部分            //在动态加载布局的时候,我们需要人为的进行计算...布局的大小...    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){        /*         * 调用这个方法的目的是为View设置大小...         * 直接设置成系统根据父容器算出的一个推荐的最小值...         * */        setMeasuredDimension(getSuggestedMinimumWidth(), getSuggestedMinimumHeight());                //获取半径...        mRadius=Math.max(getWidth(), getHeight());                final int count =getChildCount(); //获取子控件的数量,这里是7...                 int childsize=(int)(mRadius*mMaxChildDimesionRadio);        /* 这里涉及到了一个测量的模式,这个模式有三种属性...         *          * MeasureSpec.UNSPECIFIED,父视图不对子视图施加任何限制,子视图可以得到任意想要的大小;         *         * MeasureSpec.EXACTLY,父视图希望子视图的大小是specSize中指定的大小;         *               * MeasureSpec.AT_MOST,子视图的大小最多是specSize中的大小。         * */        int childMode=MeasureSpec.EXACTLY;                //对所有的子View进行迭代测量...说白了就是这7个View都得进行测量...        for(int i=0;i

 第四部分其实就是正式进行布局了,但是布局我们不能随便去布,否则不会出现想要的效果,这里涉及到了一个数学上的知识...中心位置的圆圈是很好测的,但是如何测中心圆旁边的小圆位置才是关键..因此这里用了一个数学知识...

这就是如何测出中心圆圈到小圆圆心距离的测量方法...其实就是为了求出r和小圆圆心坐标...算出了这个坐标点的位置,这样就可以进行放置了,这个坐标点的位置是一点点找出来的...需要进行尝试,只是一个计算的过程,有心的人只要研究就能弄懂...无心的人给他解释也是白搭...这就是第四部分的目的,就是为了算出位置...

//→_→ 第四部分    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        // TODO Auto-generated method stub        int layoutWidth = r - l;        int layoutHeight = b - t;                //对父容器进行布局...        int layoutRadius = Math.max(layoutWidth, layoutHeight);                final int childCount=getChildCount();        int left,top;        int radius = (int) (layoutRadius * mMaxChildDimesionRadio);        //根据子元素的个数,设置角度...        float angleDelay = 360 / (getChildCount() - 1);                for (int i = 0; i < childCount; i++)        {            final View child = getChildAt(i);                        if (child.getId() == R.id.id_circle_menu_item_center)                continue;            if (child.getVisibility() == GONE){                continue;               }            //取角度值..            StartAngle %= 360;                        float tmp = layoutRadius * 1f / 3 - 1 / 22f * layoutRadius;                                    left = layoutRadius                    / 2                    + (int) Math.round(tmp                            * Math.cos(Math.toRadians(StartAngle)) - 1 / 2f                            * radius);            top = layoutRadius                    / 2                    + (int) Math.round(tmp                            * Math.sin(Math.toRadians(StartAngle)) - 1 / 2f                            * radius);            // Log.e("TAG", "left = " + left + " , top = " + top);            //由于前面还有1/8长度用来放置那个文本框...因此需要求出整体父容器的定位点...            child.layout(left, top, left + radius, top + radius);            StartAngle += angleDelay;        }        View cView = findViewById(R.id.id_circle_menu_item_center);        cView.setOnClickListener(new OnClickListener()        {            @Override            public void onClick(View v){                            Toast.makeText(getContext(), "aa", Toast.LENGTH_LONG).show();            }        });                //设置中心...        int cl = layoutRadius / 2 - cView.getMeasuredWidth() / 2;        int cr = cl + cView.getMeasuredWidth();        cView.layout(cl, cl, cr, cr);    }

  第五部分和三四没有关联,第五部分是实现旋转过程的一个重要过程...这里我重写了dispatchTouchEvent来实现...这和上一篇是存在关联的...第五部分其实就很简单了,就是实现旋转,旋转时我们需要获取旋转的角度值和坐标值,获取坐标值的目的是为了判断角度值,角度值获取到了后,传递给第六部分开启线程...系统才会按照指定的角度对布局进行持续的刷新...

//→_→ 第五部分    private float mLastX;    private float mLastY;    private FlingRunnable mFlingRunnable; //定义了一个线程...为实现第六部分定义了一个对象...        @Override    public boolean dispatchTouchEvent(MotionEvent event){                //随手指滑动特效...        float x = event.getX();        float y = event.getY();        switch (event.getAction()){        //按下操作...事件机制自带的变量...        case MotionEvent.ACTION_DOWN:            mLastX = x;            mLastY = y;            DownAngle = getAngle(x, y);//获取角度...            DownTime = System.currentTimeMillis();  //系统的当前时间...            TmpAngle = 0;                        //如果当前在进行快速滚动,那么移除对快速移动的回调...其实就是如果这个界面正在旋转,在旋转的期间我DOWN了一下,那么直接就停止旋转...            if (isFling){                removeCallbacks(mFlingRunnable);                isFling = false;                return true ;             }                        break;        //移动操作...        case MotionEvent.ACTION_MOVE:            //获取开始和结束后的角度...我们这里移动的是角度...因此需要获取角度...            float start = getAngle(mLastX, mLastY);            float end = getAngle(x, y);                        //判断x,y的值是否在1,4象限...象限相比大家都明白,是为了获取角度值...            if (getQuadrant(x, y) == 1 || getQuadrant(x, y) == 4){             //在一四象限角度为正...                StartAngle += end - start;                TmpAngle += end - start;            }else{                StartAngle += start - end;                TmpAngle += start - end;            }                                    //重新对布局进行设置...其实就是不断刷新布局的一个过程...            requestLayout();            //将初始值设置为旋转后的值...            mLastX = x;            mLastY = y;            break;        //抬起操作...        case MotionEvent.ACTION_UP:            //计算每秒钟移动的角度...            float anglePrMillionSecond = TmpAngle * 1000                    / (System.currentTimeMillis() - DownTime);            //如果数值大于这个指定的数值,那么就会认为是加速滚动...            if (Math.abs(anglePrMillionSecond) > 230 && !isFling){                //开启一个新的线程,让其进行自由滚动...                post(mFlingRunnable = new FlingRunnable(anglePrMillionSecond));            }            if(Math.abs(anglePrMillionSecond) >230 || isFling){                return true ;             }            break;        }        return super.dispatchTouchEvent(event);            }    //这里用来测试旋转的角度...算出角度之后返回...返回给上面的方法...    private float getAngle(float xTouch, float yTouch){        double x = xTouch - (mRadius / 2d);        double y = yTouch - (mRadius / 2d);        return (float) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);    }    //在这里我们对坐标值进行一个判断..然后把坐标值返回...目的是测试角度...    private int getQuadrant(float x, float y){        int tmpX = (int) (x - mRadius / 2);        int tmpY = (int) (y - mRadius / 2);        if (tmpX >= 0){            return tmpY >= 0 ? 4 : 1;        }else{            return tmpY >= 0 ? 3 : 2;        }    }

  而第六部分其实就没什么了,因为这个界面在旋转的时候,我们不能让他一直转,迟早要停下来,因此我们需要有一个线程来帮助我们持续对界面刷新,并且控制刷新的速度,随着velocity这个值越来越小刷新的速度也就越来越慢,最后就静止不动,这样给人的感觉就是一个减速的过程...

//→_→ 第六部分    private class FlingRunnable implements Runnable{        private float velocity;        public FlingRunnable(float velocity){            this.velocity = velocity;        }        public void run(){            if ((int) Math.abs(velocity) < 20){                isFling = false;                return;            }            //减速旋转...            isFling = true;            StartAngle += (velocity / 30);            velocity /= 1.0666F;            postDelayed(this, 30);//需要保证时刻对页面进行刷新..因为始终要进行新的布局...            requestLayout();        }    }}

  第二部分其实就是加载旁边那个TextView和其背景的一个过程,并定义了一些相关资源...第一部分就更不用说了,一些变量的定义...看懂了三四五六部分,自然知道那些变量的作用了...最后贴一下xml文件和Activity的源代码....

Activity....

package com.example.cil_212_app;import android.os.Bundle;import android.app.Activity;import android.content.Intent;import android.view.KeyEvent;import android.view.Menu;public class Web extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.web_circle);    }    @Override    public boolean onKeyDown(int keyCode, KeyEvent event){        if(keyCode==KeyEvent.KEYCODE_BACK){            Intent intent=new Intent(Web.this,CoverActivity.class);            startActivity(intent);            this.finish();        }        return super.onKeyDown(keyCode, event);            }    @Override    public boolean onCreateOptionsMenu(Menu menu) {        // Inflate the menu; this adds items to the action bar if it is present.        getMenuInflater().inflate(R.menu.android, menu);        return true;    }}

xml...补充一点就是我自己定义了一个资源文件...用来存放id信息使用的....这样可以在布局中直接进行引用....

源码地址:http://files.cnblogs.com/files/RGogoing/com_Draker.zip

 

转载地址:http://zykdl.baihongyu.com/

你可能感兴趣的文章
技术大咖云集,GIAC 2017全球互联网架构大会圆满落幕
查看>>
php取整函数ceil,floor,round,intval函数的区别
查看>>
安卓应用安全指南 4.2.2 创建/使用广播接收器 规则书
查看>>
Stratus Technologies与海得控制升级长期战略合作,助力中国工业自动化与工业物联网解决方案...
查看>>
新建的SQL Server账号无法使用跟踪功能
查看>>
远程线程注入引出的问题
查看>>
「镁客·请讲」NXROBO林天麟:我们分三步走,首先要做的就是打通机器人行业的产业链...
查看>>
这款创意相机,能让盲人更真实的感触身边世界
查看>>
hdu 1285 确定比赛名次(很典型的拓扑排序)
查看>>
学习iOS【3】数组、词典和集合
查看>>
8Python全栈之路系列之Django Cookie 与Sessi
查看>>
nginx反向代理配置
查看>>
DecimalFormat用法
查看>>
流程DEMO-出差申请单
查看>>
sublime安装package control及常用插件
查看>>
vi/vim使用进阶: 指随意动,移动如飞 (一)
查看>>
zabbix3.2 snmp 监控交换机流量
查看>>
webkit 渲染机制
查看>>
unix系统安装及应用
查看>>
数据库连接学习--简单的通讯录
查看>>