前段时间公司项目要求做一个特效的滑动选择器,效果如下图的样子:
功能要求:两边的半圆形转盘可以转动,转盘上的图标也一起滚动,蓝红色图标指着的小图标变成高亮选中状态。
第一眼看到这个需求就想到这个必须要用自定义控件来做才行,于是产生了这样的思路:
半圆形的滚动的转盘自定义view继承viewgroup,重写滑动事件,自定义圆盘上图片的摆放角度,至于蓝色和红色箭头图标指向的选中状态可以用坐标数组绘制一个区域来判断是否有符合条件的图标滚动到了这个位置,如果有的话就将这个图标所在的控件透明度设置为1,如果没到这个位置就设置为非选中状态0.5透明度 ,思路这样定下来了,预计可以行得通,于是开始进行实际的尝试写代码实现这个自定义的控件和功能。
下面我直接把核心代码附上,注释比较清晰:
attrs.xml文件代码:
自定义控件的类代码:
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;
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;
private List mImageList = new ArrayList<>();
private boolean isCanClick = true;
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;
}
}
@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;
public void addonItemClick(onLogoItemClick mOnLogoItemClick) {
this.monLogoItemClick = mOnLogoItemClick;
}
public interface OnIconSelectedListener{
void onIconSelected( int pos);
}
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
然后就是你在activity中根据回调方法获取选中的对象:
//左右侧方法相同,这里列出左侧圆盘获取方法:
view.ringView_half_left.addonIconSelectedListener { position ->
// ToDo 根据postion从你的list中获取对应的选中的对象的bean类属性即可
}
最后贴下布局文件:
//这里是放半圆形转盘选择器上显示的图片list,我这里是用的xml静态传进去的,也可以改为动态方式传递
app:list_rh="@array/zodiac_list"
然后在values下面创建一个arrays.xml文件
- @drawable/zodiac_1
- @drawable/zodiac_2
- @drawable/zodiac_3
- @drawable/zodiac_4
- @drawable/zodiac_5
- @drawable/zodiac_6
- @drawable/zodiac_7
- @drawable/zodiac_8
- @drawable/zodiac_9
- @drawable/zodiac_10
- @drawable/zodiac_11
- @drawable/zodiac_12
到此就可以了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。



