自定义控件之摩天轮菜单

package com.example.buildbankmenuview;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;

/**
 * created by gongli on 2019/11/27
 */
public class buildBankMenuView extends ViewGroup {
    //6个view的坐标,刚开始不变,当监听事件发生,他们的坐标变动是一个圆形。即(x-a)^2+(y-b)^2=r;
    //这里有很多种获取view坐标的方案,反正我觉得这种简单。
    //监听事件
    //对move做判断,是向左,向右,滑动的距离大于多少,是以不同的速度绘制view。

    //外部设置过来的数据

    //内部控件数量
    int innerViewCount;

    //设置点击事件
    innerViewListener listener;

    public void setListener(innerViewListener listener) {
        this.listener = listener;
    }

    interface innerViewListener {
        void onInnerViewClick(View view, int position);

        void onInnerCenterViewClick(View view);
    }

    //设置内部小菜单需要的数据,把其添加到父view中。
    public void setInnerViewImageAndText(String[] texts, int[] images) {
        if (texts == null && images == null) {
            throw new IllegalArgumentException("非法参数异常");
        }
        innerViewCount = images == null ? texts.length : images.length;
        if (texts != null && images != null) {
            innerViewCount = texts.length;
        }
        //添加到父类布局。
        LayoutInflater mInflater = LayoutInflater.from(getContext());
        for (int i = 0; i < innerViewCount; i++) {
            final int j = i;
            final View view = mInflater.inflate(R.layout.item_innerview, this, false);
            ImageView image = view.findViewById(R.id.innerview_image);
            TextView text = view.findViewById(R.id.innerview_text);
            if (image != null) {
                image.setImageResource(images[i]);
                image.setVisibility(View.VISIBLE);
                image.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (listener != null) {
                            listener.onInnerViewClick(view, j);
                        }
                    }
                });
            }
            if (text != null) {
                text.setVisibility(View.VISIBLE);
                text.setText(texts[i]);
            }
            addView(view);
        }
    }


    public buildBankMenuView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setPadding(0, 0, 0, 0);
    }

    //我就爱把变量申明在这里,咋滴不服。
    //外部view直径
    int diameter;
    float innerViewScale = 1 / 4f;
    float innerCenterViewScale = 1 / 3f;
    float outerViewPaddingScale = 1 / 12f;
    float outerViewPaddingSize;

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int mWidthSize;
        int mHeigthSize;
        //即warp-content情况下
        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
            //当设置背景图时,拿到背景图的高宽
            mHeigthSize = getSuggestedMinimumHeight();
            mHeigthSize = mHeigthSize == 0 ? getDefaultWidthBySystem() : mHeigthSize;

            mWidthSize = getSuggestedMinimumHeight();
            mWidthSize = mWidthSize == 0 ? getDefaultWidthBySystem() : mWidthSize;


        } else {
            mWidthSize = mHeigthSize = Math.min(widthSize, heightSize);
        }
        //设置完这句,我们就可以拿到外部view的宽高。
        setMeasuredDimension(mWidthSize, mHeigthSize);
        diameter = Math.max(getMeasuredHeight(), getMeasuredWidth());
        //这里父view传向子view的测量规格是精确大小,当子view布局设为warpcontent时,完全由父view传递的尺寸决定。
        //这里是关键,需要详细看源码机制,我几句话是结论,说起来很简单,不懂你还是不懂。
        int mode = MeasureSpec.EXACTLY;
        int viewCount = getChildCount();
        int innerViewsize = (int) (diameter * innerViewScale);
        int measureSpec = -1;
        for (int i = 0; i < viewCount; i++) {
            View childView = getChildAt(i);
            if (childView.getVisibility() == GONE) {
                continue;
            }
            if (childView.getId() == R.id.innerview_center) {
                measureSpec = MeasureSpec.makeMeasureSpec((int) (diameter * innerCenterViewScale), mode);
            } else {
                measureSpec = MeasureSpec.makeMeasureSpec(innerViewsize, mode);
            }
            childView.measure(measureSpec, measureSpec);
        }
        outerViewPaddingSize = diameter * outerViewPaddingScale;
    }

    //不要问我这个方法具体做了啥,绝对不是你10分钟之内能弄明白的。
    private int getDefaultWidthBySystem() {
        WindowManager windowManager = (WindowManager)
                getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        return Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels);

    }

    float startAngle;
    float addAngle;
    float innerViewSize;
    //可以这样拿吧,大概。
    float innerCenterViewSize = innerCenterViewScale * diameter;

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        addAngle = 360 / (count - 1);
        for (int i = 0; i < count; i++) {
            View childView = getChildAt(i);
            if (childView.getId() == R.id.innerview_center) {
                continue;
            }
            if (childView.getVisibility() == GONE) {
                continue;
            }
            int left;
            int top;
            addAngle %= 360;
            innerViewSize = innerViewScale * diameter;
            //这里计算坐标
            //内部两个圆,他们圆心的距离。
            float distance = diameter / 2f - outerViewPaddingSize - innerViewSize / 2;
            //这里的坐标我得再想想。
            top = (int) (diameter / 2 +
                    Math.round(Math.sin(Math.toRadians(startAngle)) * distance - 1 / 2f * innerViewSize));
            left = (int) (diameter / 2 +
                    Math.round(Math.cos(Math.toRadians(startAngle)) * distance - 1 / 2f * innerViewSize));
            int mWidth = (int) innerViewSize;
            childView.layout(left, top, left + mWidth, top + mWidth);
            startAngle += addAngle;
        }
        //给中心view设置点击事件。
        View viewCenter = findViewById(R.id.innerview_center);
        if (viewCenter != null) {
            viewCenter.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (listener != null) {
                        listener.onInnerCenterViewClick(v);
                    }
                }
            });
        }
        int left2 = (int) (diameter / 2 - viewCenter.getMeasuredWidth() / 2);
        int top2 = left2 + viewCenter.getMeasuredWidth();
        viewCenter.layout(left2, left2,
                top2, top2);
    }

    float lastx;
    float lasty;
    boolean ifFling = false;
    int tempAngle;
    long downTime;
    int quickFlingValve=300;
    int noClickValve=3;
    autoFlingRunable flingRunable;
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        float nowx = ev.getX();
        float nowy = ev.getY();
        Log.e("gongli", "nowx=" + nowx + "###" + "nowyx=" + nowy);
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastx = nowx;
                lasty = nowy;
                tempAngle=0;
                downTime= System.currentTimeMillis();
                if (ifFling) {
                    removeCallbacks(flingRunable);
                    ifFling=false;
                    return true;
                }

                break;
            case MotionEvent.ACTION_MOVE:
                //上一次手指的位置。
                float start = getAngle(lastx, lasty);
                float end = getAngle(nowx, nowy);
                if (getQuaqrant(nowx,nowy)==1||getQuaqrant(nowx,nowy)==4){
                    startAngle+=end-start;
                    tempAngle+=end-start;
                }else {
                    startAngle+=start-end;
                    tempAngle+=start-end;
                }
                requestLayout();
                lastx=nowx;
                lasty=nowy;
                break;
            case MotionEvent.ACTION_UP:
                float anglePerSecond=tempAngle*1000/ (System.currentTimeMillis()-downTime);
                if (anglePerSecond>quickFlingValve){
                    post(flingRunable=new autoFlingRunable(anglePerSecond));
                    return true;
                }
                //这个是优化吧
//                if (tempAngle>noClickValve){
//                    return true;
//                }
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    public float getAngle(float a, float b) {
        //这里取绝对值的方案可以后面考虑下,不用象限这种方案。
//        double x = Math.abs(a - diameter / 2d);
//        double y = Math.abs(b - diameter / 2d);
        double x= a-diameter/2d;
        double y= b-diameter/2d;
        float angle = (int) (Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
        return angle;
    }

    public int getQuaqrant(float x, float y) {
        int result;
        if (x - diameter / 2 > 0) {
            result = y - diameter / 2 < 0 ? 1 : 4;
        } else {
            result = y - diameter / 2 < 0 ? 2 : 3;
        }
        return result;
    }

    private class autoFlingRunable implements Runnable{
        float speed;
        autoFlingRunable(float speed1){
            speed=speed1;
        }

        @Override
        public void run() {
            ifFling=true;
            if (speed<20){
                ifFling=false;
                return;
            }
            startAngle+=(speed/30);
            speed/=1.0666f;
            //这行代码放哪里无所谓吧。
            requestLayout();
            postDelayed(this,100);
        }
    }
}

重复造轮子练练技术,所有源码都在这里。

这个是原创链接:https://blog.csdn.net/lmj623565791/article/details/43131133#commentsedit

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值