实现圆形的按钮组控件
程序员文章站
2022-05-29 17:48:41
...
最近忙着找工作实习,就害怕企业问这个,然后自己就想着去实现一下,下面是实现的动态图演示
感觉还是听见挺简单的,就是有些判断比较麻烦,这里主要是对手势的判断,如何去判断滑动的方向。另外一点就是如何实现一个里面view的滚动效果。这个可能大家没有思路。
好的下面就讲将思路
/**
* 实现circleMenu的步骤是什么?
* 1.实现一个ViewGroup,放置这些menu,同时为了显示下面的文字,我们还需要在viewGroup里面创建
* 一个viewGroup实现垂直的布局
* 2.外层使用的是frameLayout,中心点放置一个指南针
* 3.在实现滑动的时候,我们直接可以对这个viewGroup进行旋转实现滚动的效果
*/
经过上面的分析,可能大家就不在那么蒙了,刚开始的时候我也是在想如何实现子view的滑动效果,不是说实现不了,就是非常的麻烦,想想就实现最简单的功能就是喽,就通过旋转viewGroup间接实现喽,,那么如何实现viewGroup的旋转吗?
想起来了没,,属性动画 ObjectAnimator
ok分析结束,我们开始实现
先给出xml文件
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg"
tools:context=".MainActivity">
<com.fang.circleMenu.CircleViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"></com.fang.circleMenu.CircleViewGroup>
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center"
android:src="@drawable/turnplate_mask_unlogin_normal" />
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center"
android:src="@drawable/znz" />
</FrameLayout>
这里可能会想为什么我使用frameLayout布局,这时因为我需要让这个viewGroup滚动,但是指南针是不能滚动的,直接放在viewgroup的上面就行,小技巧实现了
package com.fang.circleMenu;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* 实现circleMenu的步骤是什么?
* <p>
* 1.实现一个ViewGroup,放置这些menu,同时为了显示下面的文字,我们还需要在viewGroup里面创建
* 一个viewGroup实现垂直的布局
* <p>
* 2.外层使用的是frameLayout,中心点放置一个指南针
* <p>
* 3.在实现滑动的时候,我们直接可以对这个viewGroup进行旋转实现滚动的效果
*/
public class CircleViewGroup extends ViewGroup {
private int height;//viewGroup的宽高
private int width;//高度
private String TAG = "test";
private Context context;
private int imgWidth;//背景的宽度
private int imgHeight;//背景的高度
String[] title = {"搜索", "尺子", "分贝测试仪", "手电筒", "计算器", "sos"};
int[] path = {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};
public CircleViewGroup(Context context) {
this(context, null);
}
public CircleViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
createBackGround();
createButton();
setLayout();
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
getConstrainer();
}
/**
* 创建各个按钮,也可以通过xml实现,我这实现方式不太好控制。xl实现更加简单
*/
private void createButton() {
for (int i = 0; i < title.length; i++) {
LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
ImageView imageView = new ImageView(context);
imageView.setBackgroundResource(path[i]);
//这里主要是图片按钮实在太大,自己就做了简单的缩放
int heightImg = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics());
int widthImg = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics());
imageView.setLayoutParams(new ViewGroup.LayoutParams(heightImg, widthImg));
TextView textView = new TextView(context);
textView.setText(title[i]);
textView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
textView.setGravity(Gravity.CENTER);
linearLayout.addView(imageView);
linearLayout.addView(textView);
this.addView(linearLayout);
}
}
/**
* 得到当前的布局参数,宽高
*/
private void setLayout() {
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) this.getLayoutParams();
marginLayoutParams.width = imgWidth;
marginLayoutParams.height = imgHeight;
//设置布局参数
setLayoutParams(marginLayoutParams);
}
/**
* 创建一个背景,这里注意一下我们将图片的宽高进行保存
* 然后再从新设置viewGroup的布局宽高,这个是为了保证这个背景图片取得最佳的大小,保证整个背景不会变形,
* setLayout();
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void createBackGround() {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.circle_bg);
BitmapDrawable bitmapDrawable = new BitmapDrawable(bitmap);
imgHeight = bitmap.getHeight();
imgWidth = bitmap.getWidth();
Log.d(TAG, "onMeasure: = " + imgHeight + "width=" + imgWidth);
//设置背景
setBackground(bitmapDrawable);
}
/**
* 这里我么对添加的布局进行布局,保证位置,
*
* @param c
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean c, int l, int t, int r, int b) {
for (int i = 0; i < title.length; i++) {
View view = getChildAt(i);
setSubViewLayout(view, i);
}
}
int startY;
int startX;
int moveX;
int moveY;
/**
* 这里是对我们点击位置的进行事件的处理,看着很复杂,其实很简单,下面看看
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
//在这里我们队我们的点击事件进行设置
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
moveX = (int) event.getRawX();
moveY = (int) event.getRawY();
int offsetY = moveY - startY;
int offsetX = moveX - startX;
Log.d(TAG, "rawX=" + event.getRawX() + ";rawY=" + event.getRawY() + "getLeft=" + getLeft() + ";getTop=" + getTop() + "getRight=" + getRight() + ";getBottom=" + getBottom());
Log.d(TAG, "getX=" + getX() + ";getY=" + getY());
//根据getRawX和getRawY和园的坐标计算按下的象限值
//我们开始通过x,y的偏移量,来判断象限
if (startY > getTop() && startY < getTop() + height / 2) {//一四象限
//这是再次对象限进行判断,
if (startX > getLeft() && startX < getLeft() + width / 2) {//四象限
Log.d(TAG, "onTouchEvent: 四");
if (offsetX > 0 && offsetY < 0) {//↗
executeAnim(offsetX);//这个就是通过移动的方向,给予不同的偏移值,
} else if (offsetX < 0 && offsetY > 0) {//↙
executeAnim(offsetX);
}
} else if (startX > getLeft() + width / 2 && startX < getRight()) {//一象限
Log.d(TAG, "onTouchEvent: 一");
if (offsetX < 0 && offsetY < 0) {//↖
executeAnim(offsetX);
} else if (offsetX > 0 && offsetY > 0) {//↘
executeAnim(offsetX);
}
}
} else if (startY > getTop() + height / 2 && startY < getBottom()) {//二三象限
if (startX > getLeft() && startX < getLeft() + width / 2) {//三象限
Log.d(TAG, "onTouchEvent: 三");
if (offsetX < 0 && offsetY < 0) {//↖
executeAnim(-offsetX);
} else if (offsetX > 0 && offsetY > 0) {//↘
executeAnim(-offsetX);
}
} else if (startX > getLeft() + width / 2 && startX < getRight()) {//二象限
Log.d(TAG, "onTouchEvent: 二");
if (offsetX > 0 && offsetY < 0) {//↗
executeAnim(offsetY);
} else if (offsetX < 0 && offsetY > 0) {//↙
executeAnim(offsetY);
}
}
}
startX = moveX;
startY = moveY;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
public void executeAnim(int x) {
//我们的滚动效果是通过动画实现的,这里我们先是拿到了getRotation(),为的是是的当前的偏移量处于持续的偏移,不会产生从起始位置在重新开始
// ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "rotation", 0, x);
//上面的代码就是错误的代码,自己可以试试,你会发现第一次转动方向正确,第二次滑动方向会相反。
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "rotation", this.getRotation(), getRotation() + x);
objectAnimator.start();
// AnimatorSet animatorSet = new AnimatorSet();
// animatorSet.playTogether(objectAnimator);
// animatorSet.start();
}
public void getConstrainer() {
// DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
// width = displayMetrics.widthPixels;
// height = displayMetrics.heightPixels;
width = getMeasuredWidth();
height = getMeasuredHeight();
}
/**
* 这里就是对各个view的位置进行计算,
*
* @param subViewLayout
* @param i
*/
public void setSubViewLayout(View subViewLayout, int i) {
int degress = i * 60;
int centerX = (width - 300) / 2;
int centerY = (height - 300) / 2;//这里-300主要是发现半径太大了,视图显示不全,-300是自己测试出来比较合适
//当然我这中编写方案不是特别的规范,大家可以定义一个半径值
int left = (int) (width / 2 - centerX * Math.sin(Math.toRadians(degress)) - subViewLayout.getMeasuredWidth() / 2);
int top = (int) (height / 2 - centerY * Math.cos(Math.toRadians(degress)) - subViewLayout.getMeasuredHeight() / 2);
int right = left + subViewLayout.getMeasuredWidth();
int bottom = top + subViewLayout.getMeasuredHeight();
subViewLayout.layout(left, top, right, bottom);
}
}
上面对象限的判断我用图解释一下
当然上面的滑动效果在处理上面还是有有些问题,不是特别灵敏,可以在此改进,还有就是viewGroup在滚动式如何实现子view保持水平的显示效果。这两个都可以实现,这个自己扩展吧,如果上面的代码看不用,可以call我,会回复的
上一篇简单一点,也可以看我上一篇。
上一篇: 自定义属性相关的理解