 
         
         
         
         
        版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】如何在Android中自定义半圆形圆盘滚动选择器
如何在Android中自定义半圆形圆盘滚动选择器?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。attrs.xml文件代码:<!--自定义半圆形展示效果转盘选择器控件-->
<declare-styleable
name="ringview_half">
<attr
name="image_angle_rh"
format="integer"
/>
<attr
name="image_padding_rh"
format="integer"
/>
<attr
name="max_speed_rh"
format="integer"
/>
<attr
name="min_speed_rh"
format="integer"
/>
<attr
name="list_rh"
format="integer"
/>
<attr
name="can_scroll_rh"
format="boolean"
/>
<attr
name="is_right_select_icon_rh"
format="boolean"
/>
</declare-styleable>自定义控件的类代码:import
android.content.Context;
import
android.content.res.TypedArray;
import
android.graphics.Rect;
import
android.util.AttributeSet;
import
android.util.Log;
import
android.view.MotionEvent;
import
android.view.View;
import
android.view.ViewGroup;
import
android.widget.ImageView;
import
com.wj.R;
import
com.wj.utils.DensityUtil;
import
com.wj.utils.ScreenUtils;
import
java.util.ArrayList;
import
java.util.List;
/**
*
@time
2018/6/8
*
@author
JunJieW
*
@since
1376881525@
*
@description
自定义半圆形展示效果转盘选择器控件
*/
public
class
RingViewHalf
extends
ViewGroup
{
/**
*
上一次滑动的坐标
*/
private
float
mLastX;
private
float
mLastY;
/**
*
检测按下到抬起时使用的时间
*/
private
long
mDownTime;
/**
*
自动滚动线程
*/
private
ScrollResetRunnable
mScrollResetRunnable;
/**
*
检测按下到抬起时旋转的角度
*/
private
float
mTmpAngle;
/**
*
每秒最大移动角度
*/
private
int
mMax_Speed;
/**
*
如果移动角度达到该值,则屏蔽点击
*/
private
int
mMin_Speed;
/**
*
圆的直径
*/
private
int
mRadius;
/**
*
判断是否正在自动滚动
*/
private
boolean
isMove;
/**
*
布局滚动角度
*/
private
int
mStartAngle
=
0;
/**
*
中间条的宽度
*/
private
int
mCircleLineStrokeWidth;
/**
*
图片内容偏移角度
*/
private
int
mImageAngle;
/**
*
是否初始化布局
*/
private
boolean
isChekc
=
false;
/**
*
布局view
*/
private
List<Integer>
mImageList
=
new
ArrayList<>();
/**
*
是否可点击
*/
private
boolean
isCanClick
=
true;
/**
*
图片与环之间的padding
*/
private
int
mPadding;
/**
*
是否是右边居中的图标为选中图标
*/
private
boolean
is_right_select_icon
=
true;
/**
*
是否是右边居中的图标为选中图标
*/
private
Rect
select_icon_rect
=
new
Rect();
//是否能转动
private
boolean
mCanScrool;
public
RingViewHalf(Context
context)
{
this(context,
null,
0);
}
public
RingViewHalf(Context
context,
AttributeSet
attrs)
{
this(context,
attrs,
0);
}
public
RingViewHalf(Context
context,
AttributeSet
attrs,
int
defStyleAttr)
{
super(context,
attrs,
defStyleAttr);
//获取自定义控件设置的值
TypedArray
array
=
context.obtainStyledAttributes(attrs,
R.styleable.ringview_half,
0,
0);
mMax_Speed
=
array.getInteger(R.styleable.ringview_half_max_speed_rh,
300);
mMin_Speed
=
array.getInteger(R.styleable.ringview_half_min_speed_rh,
3);
mImageAngle
=
array.getInteger(R.styleable.ringview_half_image_angle_rh,
0);
mPadding
=
array.getInteger(R.styleable.ringview_half_image_padding_rh,
0);
mCanScrool
=
array.getBoolean(R.styleable.ringview_half_can_scroll_rh,
true);
is_right_select_icon
=
array.getBoolean(R.styleable.ringview_half_is_right_select_icon_rh,
true);
//获取xml定义的资源文件
TypedArray
mList
=
context.getResources().obtainTypedArray(array.getResourceId(R.styleable.ringview_half_list_rh,
0));
int
len
=
mList.length();
if
(len
>
0)
{
for
(int
i
=
0;
i
<
len;
i++)
mImageList.add(mList.getResourceId(i,
0));
}
else
{
mImageList.add(R.mipmap.icon);
mImageList.add(R.mipmap.icon);
mImageList.add(R.mipmap.icon);
}
mList.recycle();
array.recycle();
int
[]
location
=new
int
[2];
getLocationInWindow(location);
Log.d("locationInWindow",">>>>X=="+location[0]+"y=="+location[1]);
addImgIcon();
}
@Override
protected
void
onLayout(boolean
changed,
int
l,
int
t,
int
r,
int
b)
{
if
(!isChekc)
{
initView();
mRadius
=
getWidth();
isChekc
=
true;
}
}
/**
*
测量
*/
@Override
protected
void
onMeasure(int
widthMeasureSpec,
int
heightMeasureSpec)
{
setMeasuredDimension(widthMeasureSpec,
heightMeasureSpec);
super.onMeasure(widthMeasureSpec,
heightMeasureSpec);
int
childCount
=
this.getChildCount();
for
(int
i
=
0;
i
<
childCount;
i++)
{
View
child
=
this.getChildAt(i);
this.measureChild(child,
widthMeasureSpec,
heightMeasureSpec);
child.getMeasuredWidth();
}
}
/**
*
排版布局
*/
private
void
initView()
{
int
width
=
this.getWidth();
int
height
=
this.getHeight();
if
(width
!=
height)
{
int
min
=
Math.min(width,
height);
width
=
min;
height
=
min;
}
//不同屏幕分辨率下做不同的处理
float
instPadding
=
70f;
if
(ScreenUtils.getScreenWidth(getContext())<=720){
instPadding
=
55f;
}
//图片摆放的圆弧半径
mCircleLineStrokeWidth
=
getChildAt(0).getMeasuredHeight()
+
DensityUtil.dip2px(getContext(),instPadding)
+
mPadding;
//计算图片圆的半径
final
int
mContent
=
width
/
2
-
mCircleLineStrokeWidth
/
2;
for
(int
i
=
0;
i
<
getChildCount();
i++)
{
View
child
=
this.getChildAt(i);
//计算每个图片摆放的角度
int
mAnGle
=
360
/
mImageList.size()
*
(i
+
1)
+
mImageAngle;
//获取每个图片摆放的左上角的x和y坐标
float
left
=
(float)
(width
/
2
+
mContent
*
Math.cos(mAnGle
*
Math.PI
/
180))
-
child.getMeasuredWidth()
/
2;
float
top
=
(float)
(height
/
2
+
mContent
*
Math.sin(mAnGle
*
Math.PI
/
180))
-
child.getMeasuredHeight()
/
2;
/**
*
一四象限
*/
if
(getQuadrantByAngle(mAnGle)
==
1
||
getQuadrantByAngle(mAnGle)
==
4)
{
//
child.setRotation(mAnGle
-
270);
/**
*
二三象限
*/
}
else
{
//
child.setRotation(mAnGle
+
90);
}
child.layout((int)
left,
(int)
top,
(int)
left
+
child.getMeasuredWidth(),
(int)
top
+
child.getMeasuredHeight());
}
}
/**
*
添加子控件
*/
private
void
addImgIcon()
{
for
(int
i
=
1;
i
<
mImageList.size()
+
1;
i++)
{
//新建imageview
final
ImageView
mImageView
=
new
ImageView(getContext());
mImageView.setImageResource(mImageList.get(i
-
1));
LayoutParams
layoutParams
=
null;
mImageView.setScaleType(ImageView.ScaleType.FIT_XY);
if
(is_right_select_icon){
//右侧icon为选中状态
if
(i==mImageList.size()){
mImageView.setAlpha(1f);
layoutParams
=
new
LayoutParams(DensityUtil.dip2px(getContext(),40f),
DensityUtil.dip2px(getContext(),40f));
}else
{
mImageView.setAlpha(0.5f);
layoutParams
=
new
LayoutParams(DensityUtil.dip2px(getContext(),40f),
DensityUtil.dip2px(getContext(),40f));
}
}else
{
//
左侧icon为选中状态
if
(i==5){
mImageView.setAlpha(1f);
layoutParams
=
new
LayoutParams(DensityUtil.dip2px(getContext(),40f),
DensityUtil.dip2px(getContext(),40f));
}else
{
mImageView.setAlpha(0.5f);
layoutParams
=
new
LayoutParams(DensityUtil.dip2px(getContext(),40f),
DensityUtil.dip2px(getContext(),40f));
}
}
mImageView.setLayoutParams(layoutParams);
final
int
finalI
=
i;
//添加点击事件
mImageView.setOnClickListener(new
OnClickListener()
{
@Override
public
void
onClick(View
view)
{
if
(isCanClick)
{
//
Toast.makeText(getContext(),finalI
+
"
",
Toast.LENGTH_SHORT).show();
if
(mOnLogoItemClick
!=
null)
mOnLogoItemClick.onItemClick(view,
finalI
-
1);
}
}
});
//添加view
addView(mImageView);
}
//添加view点击事件
setOnClickListener(new
OnClickListener()
{
@Override
public
void
onClick(View
view)
{
if
(isCanClick)
{
}
}
});
}
/**
*
触摸监听
*/
@Override
public
boolean
dispatchTouchEvent(MotionEvent
event)
{
if
(mCanScrool)
{
float
x
=
event.getX();
float
y
=
event.getY();
switch
(event.getAction())
{
case
MotionEvent.ACTION_DOWN:
mLastX
=
x;
mLastY
=
y;
mDownTime
=
System.currentTimeMillis();
mTmpAngle
=
0;
//
如果当前已经在快速滚动
if
(isMove)
{
//
移除快速滚动的回调
removeCallbacks(mScrollResetRunnable);
isMove
=
false;
return
true;
}
break;
case
MotionEvent.ACTION_MOVE:
/**
*
获得开始的角度
*/
float
start
=
getAngle(mLastX,
mLastY);
/**
*
获得当前的角度
*/
float
end
=
getAngle(x,
y);
Log.e("TAG",
"start
=
"
+
start
+
"
,
end
="
+
end);
//
一四象限
if
(getQuadrant(x,
y)
==
1
||
getQuadrant(x,
y)
==
4)
{
mStartAngle
+=
end
-
start;
mTmpAngle
+=
end
-
start;
//二三象限
}
else
{
mStartAngle
+=
start
-
end;
mTmpAngle
+=
start
-
end;
}
//
重新布局
getCheck();
break;
case
MotionEvent.ACTION_UP:
//
获取每秒移动的角度
float
anglePerSecond
=
mTmpAngle
*
1000
/
(System.currentTimeMillis()
-
mDownTime);
//
如果达到最大速度
if
(Math.abs(anglePerSecond)
>
mMax_Speed
&&
!isMove)
{
//
惯性滚动
post(mScrollResetRunnable
=
new
ScrollResetRunnable(anglePerSecond));
return
true;
}
//
如果当前旋转角度超过minSpeed屏蔽点击
if
(Math.abs(mTmpAngle)
>
mMin_Speed)
{
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;
}
}
/**
*
在activity的onCreate方法中获取当前自定义view中在屏幕中的绝对坐标始终为0,
*
改成在onWindowFocusChanged函数中获取即可,这时view都已经加载完成
*
但这里特别注意一点要:如果是fragment种使用该自定义view的话,这里的方法就应该注释掉
*
因为不但获取到的矩形的值是空的,而且当你的fragment执行了跳转的逻辑后,再返回后会发
*
一种特别恶心的异常,你获取到判断选中位置的矩形的left,top,right,bottom的值会和
*
初始化的时候不一样,导致你选中时候的状态出现异常情况,本人已经被坑过,希望后面的同学
*
一定注意吸取教训
*/
@Override
public
void
onWindowFocusChanged(boolean
hasWindowFocus)
{
super.onWindowFocusChanged(hasWindowFocus);
getSelectIconReft();
}
//获取选中icon位置的矩形范围
private
void
getSelectIconReft()
{
int
[]
location
=
new
int
[2];
getLocationOnScreen(location);
//计算出右侧选中时图标的位置
if
(is_right_select_icon){
//选中的icon动态设置宽高为60,没选中宽高55,这里60/2为选中按钮的宽度或者高度的一半,即中心点
select_icon_rect.left
=
location[0]+getWidth()-mCircleLineStrokeWidth/2-DensityUtil.dip2px(getContext(),40f)/2;
select_icon_rect.top
=(location[1]+getHeight()/2)-DensityUtil.dip2px(getContext(),40f)/2;
select_icon_rect.right
=
location[0]+getWidth()-mCircleLineStrokeWidth/2+DensityUtil.dip2px(getContext(),40f)/2;
select_icon_rect.bottom
=
(location[1]+getHeight()/2)+DensityUtil.dip2px(getContext(),40f)/2;
}else
{
//计算出左侧选中时图标的位置
//选中的icon动态设置宽高为60,没选中宽高55,这里60/2为选中按钮的宽度或者高度的一半,即中心点
select_icon_rect.left
=
location[0]+mCircleLineStrokeWidth/2-DensityUtil.dip2px(getContext(),40f)/2;
select_icon_rect.top
=
(location[1]+getHeight()/2)-DensityUtil.dip2px(getContext(),40f)/2;
select_icon_rect.right
=
location[0]+mCircleLineStrokeWidth/2+DensityUtil.dip2px(getContext(),40f)/2;
select_icon_rect.bottom
=
(location[1]+getHeight()/2)+DensityUtil.dip2px(getContext(),40f)/2;
}
Log.d("onFocusChanged","getHeight=="+getChildAt(0).getHeight()+";getWidth=="+getChildAt(0).getWidth());
}
/**
*
通过角度判断象限
*/
private
int
getQuadrantByAngle(int
angle)
{
if
(angle
<=
90)
{
return
4;
}
else
if
(angle
<=
180)
{
return
3;
}
else
if
(angle
<=
270)
{
return
2;
}
else
{
return
1;
}
}
/**
*
惯性滚动
*/
private
class
ScrollResetRunnable
implements
Runnable
{
private
float
angelPerSecond;
public
ScrollResetRunnable(float
velocity)
{
this.angelPerSecond
=
velocity;
}
public
void
run()
{
//小于20停止
if
((int)
Math.abs(angelPerSecond)
<
20)
{
isMove
=
false;
return;
}
isMove
=
true;
//
滚动时候不断修改滚动角度大小
//
mStartAngle
+=
(angelPerSecond
/
30);
mStartAngle
+=
(angelPerSecond
/
40);
//逐渐减小这个值
angelPerSecond
/=
1.0666F;
postDelayed(this,
30);
//
重新布局
getCheck();
}
}
/**
*
点击事件接口
*/
public
interface
OnLogoItemClick
{
void
onItemClick(View
view,
int
pos);
}
private
OnLogoItemClick
mOnLogoItemClick;
private
OnIconSelectedListener
mOnIconSelectedListener;
/**
*
设置点击事件
*
@param
mOnLogoItemClick
*/
public
void
addOnItemClick(OnLogoItemClick
mOnLogoItemClick)
{
this.mOnLogoItemClick
=
mOnLogoItemClick;
}
/**
*
到选中位置后选中事件接口
*/
public
interface
OnIconSelectedListener{
void
onIconSelected(
int
pos);
}
/**
*
设置点击事件
*
@param
mOnIconSelectedListener
*/
public
void
addOnIconSelectedListener(OnIconSelectedListener
mOnIconSelectedListener)
{
this.mOnIconSelectedListener
=
mOnIconSelectedListener;
}
/**
*
旋转圆盘
*/
private
void
getCheck()
{
mStartAngle
%=
360;
setRotation(mStartAngle);
//改变选中的icon的状态
setSelectedIcon();
}
//改变选中的icon的状态
private
void
setSelectedIcon()
{
if
(select_icon_rect.left==0&&select_icon_rect.top==0){
//fragment中onWindowFocusChanged会出现计算select_icon_rect.left和select_icon_rect.top等于0的情况,
//
所以做下判断,如果为0则重新调用下计算方法
getSelectIconReft();
}
for
(int
j
=0;j<getChildCount();j++){
LayoutParams
layoutParams
=
null;
int
[]
location
=
new
int
[2];
getChildAt(j).getLocationOnScreen(location);
Log.d("getCheck","location[0]=="+location[0]+";select_icon_rect.left=="+select_icon_rect.left+"location[1]=="+location[1]+";select_icon_rect.top=="+select_icon_rect.top);
if
(is_right_select_icon){
//右边icon是选中状态的时候
if
(select_icon_rect.left-22<=location[0]&&location[0]<=select_icon_rect.right+22){
if
(select_icon_rect.top-22<=location[1]&&location[1]<=select_icon_rect.bottom+22){
getChildAt(j).setAlpha(1);
layoutParams
=
new
LayoutParams(DensityUtil.dip2px(getContext(),40f),
DensityUtil.dip2px(getContext(),40f));
//把选中的icon所在list中的position通过接口回传过去
if
(mOnIconSelectedListener!=null){
mOnIconSelectedListener.onIconSelected(j);
}
}else
{
getChildAt(j).setAlpha(0.5f);
layoutParams
=
new
LayoutParams(DensityUtil.dip2px(getContext(),40f),
DensityUtil.dip2px(getContext(),40f));
}
}else
{
getChildAt(j).setAlpha(0.5f);
layoutParams
=
new
LayoutParams(DensityUtil.dip2px(getContext(),40f),
DensityUtil.dip2px(getContext(),40f));
}
}else
{
//左边icon是选中状态的时候
if
(select_icon_rect.left-22<=location[0]&&location[0]<=select_icon_rect.right+22){
if
(select_icon_rect.top-22<=location[1]&&location[1]<=select_icon_rect.bottom+22){
getChildAt(j).setAlpha(1);
layoutParams
=
new
LayoutParams(DensityUtil.dip2px(getContext(),40f),
DensityUtil.dip2px(getContext(),40f));
//把选中的icon所在list中的position通过接口回传过去
if
(mOnIconSelectedListener!=null){
mOnIconSelectedListener.onIconSelected(j);
}
}else
{
getChildAt(j).setAlpha(0.5f);
layoutParams
=
new
LayoutParams(DensityUtil.dip2px(getContext(),40f),
DensityUtil.dip2px(getContext(),40f));
}
}else
{
getChildAt(j).setAlpha(0.5f);
layoutParams
=
new
LayoutParams(DensityUtil.dip2px(getContext(),40f),
DensityUtil.dip2px(getContext(),40f));
}
}
getChildAt(j).setLayoutParams(layoutParams);
getChildAt(j).invalidate();
Log.d("getChildCount","=="+j+";class=="+getChildAt(j).getClass()+";left=="+getChildAt(j).getLeft()+";top=="+getChildAt(j).getTop()+";right=="+getChildAt(j).getRight()+";bottom=="+getChildAt(j).getBottom()+";getLocationOnScreen:x="+location[0]+"y="+location[1]+";getRotationX=="+getRotationX()+";getRotationY=="+getRotationY());
}
}
}然后就是你在activity中根据回调方法获取选中的对象://左右侧方法相同,这里列出左侧圆盘获取方法:view.ringView_half_left.addOnIconSelectedListener
{
position
->
//
ToDo
根据postion从你的list中获取对应的选中的对象的bean类属性即可
}最后贴下布局文件:<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/ring_left_outside_rl"
android:layout_width="@dimen/dp_350"
android:layout_height="@dimen/dp_350"
android:layout_alignParentLeft="true"
android:layout_marginLeft="-240dp">
<com.wj.views.RingViewHalf
android:id="@+id/ringView_half_left"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_compatibility"
app:image_angle_rh="15"
app:image_padding_rh="20"
app:is_right_select_icon_rh="true"
app:list_rh="@array/zodiac_list"
/>
<ImageView
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/dp_220"
android:src="@drawable/icon_match_boy"
/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/ring_right_outside_rl"
android:layout_width="@dimen/dp_350"
android:layout_height="@dimen/dp_350"
android:layout_alignParentRight="true"
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年三级钳工考试试卷及答案
- 2025年实验室技师血液常规检查规范操作考核试题及答案解析
- 2025年司法考试专业资格(律师资格考试)备考题库及答案解析
- 城市轨道考试试卷及答案
- 采矿专业面试题库大全及答案
- 校园安全教育题库学生及答案解析
- 2025年邮政简答题题库及答案
- 下半年软件设计师答案(下午试题)
- 导游资格考试题库及答案大全
- 2025年神经外科医生神经外科手术治疗流程模拟考试试题及答案解析
- 2025年下半年江苏盐城市亭湖区城管协管员招考94人易考易错模拟试题(共500题)试卷后附参考答案
- 2025及未来5年中国电子记事本市场调查、数据监测研究报告
- 2025-2026学年上学期高一英语人教新版期中必刷常考题之完形填空
- 2025-2026学年人教版(2024)八年级英语上学期期中考试模拟测试卷(含答案)
- 2025年海南省三亚市辅警考试真题及答案
- 安全风险管控“六项机制”监理实施细则(水利工程)
- 辽宁省名校联盟2025年高三10月份联合考试 生物试卷(含答案详解)
- 2025秋期版国开电大本科《心理学》一平台形成性考核练习1至6在线形考试题及答案
- 2025年餐饮服务食品安全管理员考试试题与答案
- 2025江苏苏州张家港市基层公共服务岗位招聘14人(第二批)笔试模拟试题及答案解析
- 川教版(2024)四年级上册(2024)信息科技 第一节 奇妙纷呈的数据 课件
 
            
评论
0/150
提交评论