欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

实现圆形的按钮组控件

程序员文章站 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我,会回复的

上一篇简单一点,也可以看我上一篇。