android自定义view圆,Android自定义view实现圆形与半圆形菜单

本文介绍了如何模仿鸿洋大大设计的圆形菜单并进行改造,创建一个可定制的圆形与半圆形菜单。文章详细讲解了功能介绍、代码实现,包括控件的尺寸测量、布局、事件处理和数学计算,以及如何通过Fragment实现菜单项的切换。同时,还提供了源码链接供读者参考和学习。
摘要由CSDN通过智能技术生成

前不久看到鸿洋大大的圆形菜单,就想开始模仿,因为实在是太酷了,然后自己根据别人(zw哥)给我讲的一些思路、一些分析,就开始改造自己的圆形菜单了。

文章结构:1.功能介绍以及展示;2.部分代码讲解;3.大致可以实现的UI效果展示讲解。4.源码附送。

一、功能介绍以及展示

73d99feb6fa6cb0ceeeca00bf978bae3.gif

第一个展示是本控件的原样。但是我们可以使用很多技巧去达到我们的商业UI效果嘛。

e7653ec2f3d96822cef8f13b944cf341.gif

这里给出的是本博客作品demo的展示图以及第三点的联动展示,可见是一圆型菜单,相较于鸿洋大大的那个圆形菜单多了一些需求:

1.到时候展示只需要半圆的转盘。

2.在规定的角度不能让他们自动旋转(涉及延伸的一些数学计算,一会重点讲解)。

3.要绑定fragment。

4.一个缓冲角度,即我们将要固定几个位置,而不是任意位置。我们要设计一个可能的角度去自动帮他选择。

二、代码讲解:

结合实际使用的方式来讲解。分为:1.调用方式;2.此控件onMeasure方法;3.onLayout方法的作用;4.此控件事件机制dispatchTouchEvent的使用;5.数学计算—一个缓冲角度。

(1)调用方式 :(代码为展示区下方的效果代码)

//采用的是联动,使用Fragment管理器FragmentTransaction去实现fragment管理

package com.fuzhucheng.circlemenu;

import android.os.Bundle;

import android.support.v4.app.FragmentTransaction;

import android.support.v7.app.AppCompatActivity;

import android.view.KeyEvent;

import android.view.View;

import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

private UpCircleMenuLayout myCircleMenuLayout;

//四个fragment页面

private HomepageFragment homepageFragment;

private SettingFragment settingFragment;

private HistoryFragment historyFragment;

private FourthFragment fourthFragment;

private FifthFragment fifthFragment;

private String[] mItemTexts = new String[]{"安全中心 ", "特色服务", "投资理财",

"转账汇款", "我的账户", "安全中心", "特色服务", "投资理财", "转账汇款", "我的账户"};

private int[] mItemImgs = 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_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};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//第一次初始化首页默认显示第一个fragment

initFragment1();

myCircleMenuLayout = (UpCircleMenuLayout) findViewById(R.id.id_mymenulayout);

myCircleMenuLayout.setMenuItemIconsAndTexts(mItemImgs);//一句设置图片

myCircleMenuLayout.setOnMenuItemClickListener(new UpCircleMenuLayout.OnMenuItemClickListener() {

@Override

public void itemClick(int pos) {

Toast.makeText(MainActivity.this, mItemTexts[pos],

Toast.LENGTH_SHORT).show();

switch (pos) {

case 0:

initFragment1();

setTitle("安全中心");

break;

case 1:

initFragment2();

setTitle("特色服务");

break;

case 2:

initFragment3();

setTitle("投资理财");

break;

case 3:

initFragment4();

setTitle("转账汇款");

break;

case 4:

initFragment5();

setTitle("我的账户");

break;

case 5:

initFragment1();

setTitle("安全中心");

break;

case 6:

initFragment2();

setTitle("特色服务");

break;

case 7:

initFragment3();

setTitle("投资理财");

break;

case 8:

initFragment4();

setTitle("转账汇款");

break;

case 9:

initFragment5();

setTitle("我的账户");

break;

}

}

@Override

public void itemCenterClick(View view) {

Toast.makeText(MainActivity.this,

"you can do something just like ccb ",

Toast.LENGTH_SHORT).show();

}

});

}

//显示第一个fragment

private void initFragment1(){

//开启事务,fragment的控制是由事务来实现的

homepageFragment = new HomepageFragment();

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

transaction.replace(R.id.fragment_tv,homepageFragment);

transaction.addToBackStack(null);

transaction.commit();

}

//显示第二个fragment

private void initFragment2(){

//开启事务,fragment的控制是由事务来实现的

settingFragment = new SettingFragment();

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

transaction.replace(R.id.fragment_tv,settingFragment);

transaction.addToBackStack(null);

transaction.commit();

}

private void initFragment3(){

//开启事务,fragment的控制是由事务来实现的

historyFragment = new HistoryFragment();

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

transaction.replace(R.id.fragment_tv,historyFragment);

transaction.addToBackStack(null);

transaction.commit();

}

private void initFragment4(){

//开启事务,fragment的控制是由事务来实现的

fourthFragment = new FourthFragment();

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

transaction.replace(R.id.fragment_tv,fourthFragment);

transaction.addToBackStack(null);

transaction.commit();

}

private void initFragment5(){

//开启事务,fragment的控制是由事务来实现的

fifthFragment = new FifthFragment();

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

transaction.replace(R.id.fragment_tv,fifthFragment);

transaction.addToBackStack(null);

transaction.commit();

}

public boolean onKeyDown(int keyCode, KeyEvent event) {

if (keyCode == KeyEvent.KEYCODE_BACK

&& event.getRepeatCount() == 0) {

finish();

return true;

}

return super.onKeyDown(keyCode, event);

}

}

(2)此控件onMeasure方法讲解:重点讲解迭代测量

/**

* 设置布局的宽高,并策略menu item宽高

*/

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int resWidth = 0;

int resHeight = 0;

double startAngle = mStartAngle;

double angle = 360 / 10; //我们传入了10个孩子

/**

* 根据传入的参数,分别获取测量模式和测量值

*/

int width = MeasureSpec.getSize(widthMeasureSpec);

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int height = MeasureSpec.getSize(heightMeasureSpec);

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

/**

* 如果宽或者高的测量模式非精确值

*/

if (widthMode != MeasureSpec.EXACTLY

|| heightMode != MeasureSpec.EXACTLY) {

// 主要设置为背景图的高度

resWidth = getDefaultWidth();

resHeight = (int) (resWidth * DEFAULT_BANNER_HEIGTH /

DEFAULT_BANNER_WIDTH);

} else {

// 如果都设置为精确值,则直接取小值;

resWidth = resHeight = Math.min(width, height);

}

setMeasuredDimension(resWidth, resHeight);

// 获得直径

mRadius = Math.max(getMeasuredWidth(), getMeasuredHeight());

// menu item数量

final int count = getChildCount();

// menu item尺寸

int childSize;

// menu item测量模式

int childMode = MeasureSpec.EXACTLY;

// 迭代测量:根据孩子的数量进行遍历,为每一个孩子测量大小,设置监听回调。

for (int i = 0; i < count; i++) {

final View child = getChildAt(i);

startAngle = startAngle % 360;

if (startAngle > 269 && startAngle < 271 && isTouchUp) {

mOnMenuItemClickListener.itemClick(i); //设置监听回调。

mCurrentPosition = i; //本次使用mCurrentPosition,只是把他作为一个temp变量,可以有更多的使用,比如动态设置每个孩子相隔的角度

childSize = DensityUtil.dip2px(getContext(), RADIO_TOP_CHILD_DIMENSION);//设置大小

} else {

childSize = DensityUtil.dip2px(getContext(), RADIO_DEFAULT_CHILD_DIMENSION);//设置大小

}

if (child.getVisibility() == GONE) {

continue;

}

// 计算menu item的尺寸;以及和设置好的模式,去对item进行测量

int makeMeasureSpec = -1;

makeMeasureSpec = MeasureSpec.makeMeasureSpec(childSize,

childMode);

child.measure(makeMeasureSpec, makeMeasureSpec);

startAngle += angle;

}

//item容器内边距

mPadding = DensityUtil.dip2px(getContext(), RADIO_MARGIN_LAYOUT);

}

onMeasure深入:View在屏幕上显示出来要先经过measure(计算)和layout(布局)。这方法作用就是计算出自定义View的宽度和高度。这个计算的过程参照父布局给出的大小,以及自己特点算出结果 。当然,还有相关的尺寸测量模式。此处奉上一篇好博文:onMeasure理解。此外,我还在这方法里作为监听回调的设置!!而为控件设置图片可以直接使用我们下面设计的方法:setMenuItemIconsAndTexts一句收工。

(3)onLayout方法的讲解:(此处的圆的数学计算布置图标围绕圆位置可见鸿洋大大的推荐,讲得很清楚,当然我下面也会略微讲解下)

/**

* 设置menu item的位置

*/

@Override

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

int layoutRadius = mRadius;

// Laying out the child views

final int childCount = getChildCount();

int left, top;

// menu item 的尺寸

int cWidth;

// 根据menu item的个数,计算角度

float angleDelay = 360 / 10;

// 遍历去设置menuitem的位置

for (int i = 0; i < childCount; i++) {

final View child = getChildAt(i);

//根据孩子遍历,设置中间顶部那个的大小以及其他图片大小。

if (mStartAngle > 269 && mStartAngle < 271 && isTouchUp) {

cWidth = DensityUtil.dip2px(getContext(), RADIO_TOP_CHILD_DIMENSION);

child.setSelected(true);

} else {

cWidth = DensityUtil.dip2px(getContext(), RADIO_DEFAULT_CHILD_DIMENSION);

child.setSelected(false);

}

if (child.getVisibility() == GONE) {

continue;

}

//大于360就取余归于小于360度

mStartAngle = mStartAngle % 360;

float tmp = 0;

//计算图片布置的中心点的圆半径。就是tmp

tmp = layoutRadius / 2f - cWidth / 2 - mPadding;

// tmp cosa 即menu item中心点的横坐标。计算的是item的位置,是计算位置!!!

left = layoutRadius

/ 2

+ (int) Math.round(tmp

* Math.cos(Math.toRadians(mStartAngle)) - 1 / 2f

* cWidth) + DensityUtil

.dip2px(getContext(), 1);

// tmp sina 即menu item的纵坐标

top = layoutRadius

/ 2

+ (int) Math.round(tmp

* Math.sin(Math.toRadians(mStartAngle)) - 1 / 2f * cWidth) + DensityUtil

.dip2px(getContext(), 8);

//接着当然是布置孩子的位置啦,就是根据小圆的来布置的

child.layout(left, top, left + cWidth, top + cWidth);

// 叠加尺寸

mStartAngle += angleDelay;

}

}

给出鸿洋大大的计算小圆的思路图:

7abbecbc3c109acefb8f4797cedd33d5.png

(4)此控件事件机制dispatchTouchEvent的使用:

//dispatchTouchEvent是处理触摸事件分发,事件(多数情况)是从Activity的dispatchTouchEvent开始的。执行super.dispatchTouchEvent(ev),事件向下分发。

//onTouchEvent是View中提供的方法,ViewGroup也有这个方法,view中不提供onInterceptTouchEvent。view中默认返回true,表示消费了这个事件。

@Override

public boolean dispatchTouchEvent(MotionEvent event) {

float x = event.getX();

float y = event.getY();

getParent().requestDisallowInterceptTouchEvent(true);

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

//直接就是获取x,y值了,还有一个DownTime(附送)

mLastX = x;

mLastY = y;

mDownTime = System.currentTimeMillis();

mTmpAngle = 0;

break;

case MotionEvent.ACTION_MOVE:

isTouchUp = false; //注意isTouchUp 这个标记量!!!

/**

* 获得开始的角度

*/

float start = getAngle(mLastX, mLastY);

/**

* 获得当前的角度

*/

float end = getAngle(x, y);

// 如果是一、四象限,则直接end-start,角度值都是正值

if (getQuadrant(x, y) == 1 || getQuadrant(x, y) == 4) {

mStartAngle += end - start;

mTmpAngle += end - start;//按下到抬起时旋转的角度

} else

// 二、三象限,色角度值是负值

{

mStartAngle += start - end;

mTmpAngle += start - end;

}

// 重新布局

if (mTmpAngle != 0) {

requestLayout();

}

mLastX = x;

mLastY = y;

break;

case MotionEvent.ACTION_UP:

//当手指UP啦,就是关键啦,一个缓冲角度,即我们将要固定几个位置,而不是任意位置。我们要设计一个可能的角度去自动帮他选择。

backOrPre();

break;

}

return super.dispatchTouchEvent(event);

}

MotionEvent事件机制:(此控件我只用了三个)主要的事件类型有:ACTION_DOWN: 表示用户开始触摸。ACTION_MOVE: 表示用户在移动(手指或者其他)。ACTION_UP:表示用户抬起了手指。

(5)数学计算—一个缓冲角度。

private void backOrPre() { //缓冲的角度。即我们将要固定几个位置,而不是任意位置。我们要设计一个可能的角度去自动帮他选择。

isTouchUp = true;

float angleDelay = 360 / 10; //这个是每个图形相隔的角度

//我们本来的上半圆的图片角度应该是:18,54,90,126,162。所以我们这里是:先让当前角度把初始的18度减去再取余每个图形相隔角度。得到的是什么呢?就是一个图片本来应该在的那堆角度。所以如果是就直接return了。

if ((mStartAngle-18)%angleDelay==0){

return;

}

float angle = (float)((mStartAngle-18)%36); //angle就是那个不是18度开始布局,然后是36度的整数的多出来的部分角度

//以下就是我们做的缓冲角度处理啦,如果多出来的部分角度大于图片相隔角度的一半就往前进一个,如果小于则往后退一个。

if (angleDelay/2 > angle){

mStartAngle -= angle;

}else if (angleDelay/2

mStartAngle = mStartAngle - angle + angleDelay; //mStartAngle就是当前角度啦,取余36度就是多出来的角度,拿这个多出来的角度去数据处理。

}

//然后重新布局onlayout

requestLayout();

}

至于其他小的方法详情,可见源代码,有详细解释。

源码传送门:github地址:Android-自定义view之圆形与“半圆形”菜单 喜欢的可以star或fork啦,谢谢!

好了,Android-自定义view之圆形与“半圆形”菜单讲完了。本博客是经过仔细研究鸿洋大大的圆形菜单博客的,并在这里做出进一步拓展以及写出自己的理解。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android自定义View圆形刻度在实现上相对简单,主要步骤如下: 1. 创建一个继承自View自定义View类,命名为CircleScaleView。 2. 在该自定义View的构造方法中完成必要的初始化工作,例如设置画笔、设置View的宽高、设置绘制模式等。 3. 重写onMeasure()方法,设置View的尺寸大小。可以根据自定义的需求来决定View的宽高。 4. 重写onDraw()方法,完成绘制整个圆形刻度的逻辑。 5. 在onDraw()方法中,首先通过getMeasuredWidth()和getMeasuredHeight()方法获取到View的宽高,然后计算心的坐标。 6. 接着,使用Canvas对象的drawArc()方法来绘制弧,根据需求设置弧的起始角度和扫描角度。 7. 再然后,通过循环绘制每个刻度线,可以使用Canvas对象的drawLine()方法来绘制。 8. 最后,根据需要绘制刻度值或其他其他附加元素,例如心的标记。 9. 至此,整个圆形刻度的绘制逻辑就完成了。 10. 在使用该自定义View的时候,可以通过添加该View到布局文件中或者在代码中动态添加,并按需设置相应的属性。 需要注意的是,自定义圆形刻度的具体样式和行为取决于项目需求,上述步骤仅为基础实现框架,具体细节需要根据实际情况进行相应的调整。 ### 回答2: 在Android实现一个圆形刻度的自定义View有几个步骤。 首先,我们需要创建一个自定义View类,继承自View或者它的子类(如ImageView)。 接下来,在自定义View的构造方法中,初始化一些必要的属性,比如画笔的颜色、宽度等。我们可以使用Paint类来设置这些属性。 然后,我们需要在自定义View的onMeasure方法中设置View的宽度和高度,确保View在屏幕上正常显示。一种常见的实现方式是将宽度和高度设置为相同的值,使得View呈现出圆形的形状。 接着,在自定义View的onDraw方法中,我们可以利用画笔来绘制圆形刻度。可以使用canvas.drawCircle方法来绘制一个圆形,使用canvas.drawLine方法绘制刻度线。我们可以根据需要,定义不同的刻度颜色和宽度。 最后,我们可以在自定义View的其他方法中,添加一些额外的逻辑。比如,在onTouchEvent方法中处理触摸事件,以实现拖动刻度的功能;在onSizeChanged方法中根据View的尺寸调整刻度的大小等等。 当我们完成了自定义View的代码编写后,我们可以在布局文件中使用这个自定义View。通过设置布局文件中的属性,可以进一步自定义View的外观和行为。 总之,实现一个圆形刻度的自定义View,我们需要定义一个自定义View类,并在其中使用画笔来绘制圆形和刻度。通过处理一些事件和属性,我们可以实现更多的功能和样式。以上就是简单的步骤,可以根据需要进行更加详细的实现。 ### 回答3: Android自定义View圆形刻度可以通过以下步骤实现。 首先,我们需要创建一个自定义View,继承自View类,并重写onDraw方法。在该方法中,我们可以自定义绘制的内容。 其次,我们需要定义一个圆形的刻度尺背景,可以使用Canvas类提供的drawCircle方法来绘制实心或空心。 接着,我们可以通过Canvas类的drawLine方法来绘制刻度线。根据刻度的数量,可以计算出每个刻度之间的角度,然后循环绘制出所有的刻度线。 然后,我们可以通过Canvas类的drawText方法来绘制刻度的值。根据刻度线的角度和半径,可以计算出刻度的坐标,然后将刻度的值绘制在指定的位置上。 最后,我们可以通过在自定义View的构造方法中获取相关的参数,如刻度的最大值、最小值、当前值等,然后根据这些参数来计算刻度的位置和值。 在使用自定义View时,可以通过设置相关的属性来改变刻度的样式和位置。例如,可以设置刻度线的颜色、粗细、长度等,也可以设置刻度值的颜色、大小等。 通过以上步骤,我们就可以实现一个圆形刻度尺的自定义View。在使用时,可以根据需要自行调整绘制的样式和逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值