在项目中看着这个旋转效果挺炫的,就抽取出来做个记录。主要是使用CarrouselLayout 稍微修改

CarrouselLayout代码Demo下载z地址:GitHub

https://github.com/lyfkai/AndroidCarrouselLayout

主要代码如下 :

public class CarrouselLayout extends RelativeLayout {

private Context mContext;
    //自动旋转 默认不自动
    private boolean mAutoRotation;

//旋转间隔时间  默认设置为2秒
    private int mRotationTime;

//旋转木马旋转半径  圆的半径
    private float mCarrouselR;

//camera和旋转木马距离
    private float mDistance = 2f * mCarrouselR;

//旋转方向 分0顺时针和 1逆时针 俯视旋转木马看
    private int mRotateDirection;

//handler
    private CarrouselRotateHandler mHandler;

//手势处理
    private GestureDetector mGestureDetector;

//x旋转
    private int mRotationX;

//Z旋转
    private int mRotationZ;

//旋转的角度
    private float mAngle = 0;

//旋转木马子view
    private List<View> mCarrouselViews = new ArrayList<>();

//旋转木马子view的数量
    private int viewCount;

//半径扩散动画
    private ValueAnimator mAnimationR;

//记录最后的角度 用来记录上一次取消touch之后的角度
    private float mLastAngle;

//是否在触摸
    private boolean isTouching;

//旋转动画
    private ValueAnimator restAnimator;

//选中item
    private int selectItem;

//item选中回调接口
    private OnCarrouselItemSelectedListener mOnCarrouselItemSelectedListener;

//item点击回调接口
    private OnCarrouselItemClickListener mOnCarrouselItemClickListener;

//x轴旋转动画
    private ValueAnimator xAnimation;

//z轴旋转动画
    private ValueAnimator zAnimation;

private Boolean isfinish = true;//惯性动画是否结束
    private boolean isFling;

public CarrouselLayout(Context context) {
        this(context, null);
    }

public CarrouselLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

public CarrouselLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

private void init(Context context, AttributeSet attrs) {
        this.mContext = context;
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarrouselLayout);
        mAutoRotation = typedArray.getBoolean(R.styleable.CarrouselLayout_autoRotation, false);
        mRotationTime = typedArray.getInt(R.styleable.CarrouselLayout_rotationTime, 2000);
        mCarrouselR = typedArray.getDimension(R.styleable.CarrouselLayout_r, 200);
        mRotateDirection = typedArray.getInt(R.styleable.CarrouselLayout_rotateDirection, 0);
        typedArray.recycle();
        mGestureDetector = new GestureDetector(context, getGestureDetectorController());
        initHandler();
    }

/**
     * 初始化handler对象
     */
    private void initHandler() {
        mHandler = new CarrouselRotateHandler(mAutoRotation, mRotationTime, mRotateDirection) {
            @Override
            public void onRotating(CarrouselRotateDirection rotateDirection) {//接受到需要旋转指令
                try {
                    if (viewCount != 0) {//判断自动滑动从那边开始
                        int perAngle = 0;
                        switch (rotateDirection) {
                            case clockwise:
                                perAngle = 360 / viewCount;
                                break;
                            case anticlockwise:
                                perAngle = -360 / viewCount;
                                break;
                        }
                        if (mAngle == 360) {
                            mAngle = 0f;
                        }

if (isfinish)
                            startAnimRotation(mAngle + perAngle, null);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

}
        };
    }

private GestureDetector.SimpleOnGestureListener getGestureDetectorController() {

return new GestureDetector.SimpleOnGestureListener() {

@Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                isFling = true;

if (e2.getX() - e1.getX() < 0) { // 左滑
                    setAutoScrollDirection(CarrouselRotateDirection.clockwise);
                } else {
                    setAutoScrollDirection(CarrouselRotateDirection.anticlockwise);
                }
                return true;
            }

@Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                isFling = false;
                if (distanceX > 0) { // 左滑
                    setAutoScrollDirection(CarrouselRotateDirection.clockwise);
                } else {
                    setAutoScrollDirection(CarrouselRotateDirection.anticlockwise);
                }
                //转换成弧度
                double radians = Math.toRadians(mRotationZ);
                //Math.cos(radians) 返回对应的radians弧度的余弦值
                mAngle += Math.cos(radians) * (distanceX / 4) + Math.sin(radians) * (distanceY / 4);
                // Log.e("mAngle", mAngle + "");
                Log.e("mRotationZ", mRotationZ + "");
                //初始化
                refreshLayout();
                return true;
            }

};
    }

/**
     * 初始化 计算平均角度后各个子view的位置
     */
    public void refreshLayout() {
        for (int i = 0; i < mCarrouselViews.size(); i++) {
            double radians = mAngle + 180 - i * 360 / viewCount;
            float x0 = (float) Math.sin(Math.toRadians(radians)) * mCarrouselR;
            float y0 = (float) Math.cos(Math.toRadians(radians)) * mCarrouselR;
            float scale0 = (mDistance - y0) / (mDistance + mCarrouselR);
            mCarrouselViews.get(i).setScaleX(scale0 < 0.5f ? 0.5f : scale0);
            mCarrouselViews.get(i).setScaleY(scale0 < 0.5f ? 0.5f : scale0);
            if (mCarrouselViews.get(i) instanceof RelativeLayout) {
                if (((RelativeLayout) mCarrouselViews.get(i)).getChildCount() >= 2) {
                    if (((RelativeLayout) mCarrouselViews.get(i)).getChildAt(0) instanceof ImageView) {
                        int value = Float.valueOf((scale0 < 0.5f ? 0.5f : scale0) * 255).intValue();
                        ((ImageView) ((RelativeLayout) mCarrouselViews.get(i)).getChildAt(0)).setImageAlpha(value);
//                        ((ImageView)((RelativeLayout) mCarrouselViews.get(i)).getChildAt(0)).setAlpha(scale0);
                    }
//                    ((RelativeLayout) mCarrouselViews.get(i)).getChildAt(0).setAlpha(scale0);
                    if (((RelativeLayout) mCarrouselViews.get(i)).getChildAt(1) instanceof TextView) {
                        ((RelativeLayout) mCarrouselViews.get(i)).getChildAt(1).setAlpha(scale0 < 0.5f ? 0.5f : scale0);
                    }
                }
            }
            float rotationX_y = (float) Math.sin(Math.toRadians(mRotationX * Math.cos(Math.toRadians(radians)))) * mCarrouselR;
            float rotationZ_y = -(float) Math.sin(Math.toRadians(-mRotationZ)) * x0;
            float rotationZ_x = (((float) Math.cos(Math.toRadians(-mRotationZ)) * x0) - x0);
            mCarrouselViews.get(i).setTranslationX(x0 + rotationZ_x);
            mCarrouselViews.get(i).setTranslationY(rotationX_y + rotationZ_y);
        }
        List<View> arrayViewList = new ArrayList<>();
        arrayViewList.clear();
        for (int i = 0; i < mCarrouselViews.size(); i++) {
            arrayViewList.add(mCarrouselViews.get(i));
        }
        sortList(arrayViewList);
        postInvalidate();
    }

/**
     * 排序
     * 對子View 排序,然后根据变化选中是否重绘,这样是为了实现view 在显示的时候来控制当前要显示的是哪三个view,可以改变排序看下效果
     *
     * @param list
     */
    @SuppressWarnings("unchecked")
    private <T> void sortList(List<View> list) {
        @SuppressWarnings("rawtypes")
        Comparator comparator = new SortComparator();
        T[] array = list.toArray((T[]) new Object[list.size()]);
        Arrays.sort(array, comparator);
        int i = 0;
        ListIterator<T> it = (ListIterator<T>) list.listIterator();
        while (it.hasNext()) {
            it.next();
            it.set(array[i++]);
        }
        for (int j = 0; j < list.size(); j++) {
            list.get(j).bringToFront();
        }
    }

/**
     * 筛选器
     */
    private class SortComparator implements Comparator<View> {
        @Override
        public int compare(View o1, View o2) {
            return (int) (1000 * o1.getScaleX() - 1000 * o2.getScaleX());
        }
    }

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        refreshLayout();
        if (mAutoRotation) {
            mHandler.sendEmptyMessage(CarrouselRotateHandler.mMsgWhat);
        }
    }

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            checkChildView();
            startAnimationR();
        }
    }

/**
     * 旋转木马半径打开动画
     */
    public void startAnimationR() {
        startAnimationR(1f, mCarrouselR);
    }

/**
     * 旋转木马半径动画
     *
     * @param isOpen 是否打开  否则关闭
     */
    public void startAnimationR(boolean isOpen) {
        if (isOpen) {
            startAnimationR(1f, mCarrouselR);
        } else {
            startAnimationR(mCarrouselR, 1f);
        }
    }

/**
     * 半径扩散、收缩动画 根据设置半径来实现
     *
     * @param from
     * @param to
     */
    public void startAnimationR(float from, float to) {
        mAnimationR = ValueAnimator.ofFloat(from, to);
        mAnimationR.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mCarrouselR = (Float) valueAnimator.getAnimatedValue();
                refreshLayout();
            }
        });
        mAnimationR.setInterpolator(new DecelerateInterpolator());
        mAnimationR.setDuration(0);
        mAnimationR.start();
    }

public void checkChildView() {
        //先清空views里边可能存在的view防止重复
        for (int i = 0; i < mCarrouselViews.size(); i++) {
            mCarrouselViews.remove(i);
        }
        final int count = getChildCount(); //获取子View的个数
        viewCount = count;
        for (int i = 0; i < count; i++) {
            final View view = getChildAt(i); //获取指定的子view
            final int position = i;
            mCarrouselViews.add(view);
            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mOnCarrouselItemClickListener != null) {
                        mOnCarrouselItemClickListener.onItemClick(view, position);
                    }
                }
            });

}

}

/**
     * 复位
     */
    private void restView() {
        if (viewCount == 0) {
            return;
        }
        float resultAngle = 0;
        //平均角度
        float averageAngle = 360 / viewCount;
        if (mAngle < 0) {
            averageAngle = -averageAngle;
        }
        float minvalue = (int) (mAngle / averageAngle) * averageAngle;//最小角度
        float maxvalue = (int) (mAngle / averageAngle) * averageAngle + averageAngle;//最大角度
        if (mAngle >= 0) {//分为是否小于0的情况
            if (mAngle - mLastAngle > 0) {
                resultAngle = maxvalue;
            } else {
                resultAngle = minvalue;
            }
        } else {
            if (mAngle - mLastAngle < 0) {
                resultAngle = maxvalue;
            } else {
                resultAngle = minvalue;
            }
        }
        startAnimRotation(resultAngle, null);
    }

/**
     * 动画旋转
     *
     * @param resultAngle
     * @param complete
     */
    private void startAnimRotation(float resultAngle, final Runnable complete) {
        if (mAngle == resultAngle) {
            return;
        }
        if (!isfinish) {
            restAnimator = ValueAnimator.ofFloat(mAngle, mAngle + (resultAngle - mAngle) * 5);
            //设置旋转匀速插值器
            restAnimator.setInterpolator(new DecelerateInterpolator());
            restAnimator.setDuration(1000);
        } else {
            restAnimator = ValueAnimator.ofFloat(mAngle, resultAngle);
            //设置旋转匀速插值器
            restAnimator.setInterpolator(new LinearInterpolator());
            restAnimator.setDuration(8000);
        }

restAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                if (isTouching == false) {
                    mAngle = (Float) animation.getAnimatedValue();
                    refreshLayout();
                }
            }
        });
        restAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

}

@Override
            public void onAnimationEnd(Animator animation) {
                if (isTouching == false) {
                    selectItem = calculateItem();
                    if (selectItem < 0) {
                        selectItem = viewCount + selectItem;
                    }
                    if (mOnCarrouselItemSelectedListener != null) {
                        mOnCarrouselItemSelectedListener.selected(mCarrouselViews.get(selectItem), selectItem);
                    }

if (isfinish == false) {
                        isfinish = true;
                    }
                    isFling = false;
                }
            }

@Override
            public void onAnimationCancel(Animator animation) {

}

@Override
            public void onAnimationRepeat(Animator animation) {

}
        });
        restAnimator.start();
    }

/**
     * 通过角度计算是第几个item
     *
     * @return
     */
    private int calculateItem() {
        return (int) (mAngle / (360 / viewCount)) % viewCount;
    }

/**
     * 触摸停止计时器
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return setCanAutoRotation(ev);
    }

/**
     * 触摸方法
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }

/**
     * 触摸时停止自动加载
     *
     * @param event
     */

float DownX = 0;
    float DownY = 0;
    long currentMS = 0;

float moveX;
    float moveY;
    long moveTime;

public boolean setCanAutoRotation(MotionEvent event) {

boolean result = mGestureDetector.onTouchEvent(event);
        if (result) {
            this.getParent().requestDisallowInterceptTouchEvent(true);//通知父控件勿拦截本控件
        }
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
            if (!result) {
                this.getParent().requestDisallowInterceptTouchEvent(true);//通知父控件勿拦截本控件
            }
        }

switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastAngle = mAngle;
                DownX = event.getX();//float DownX
                DownY = event.getY();//float DownY
                currentMS = System.currentTimeMillis();//long currentMS     获取系统时间

break;
            case MotionEvent.ACTION_MOVE:
                moveX = event.getX() - DownX;//X轴距离
                moveY = event.getY() - DownY;//Y轴距离
                moveTime = System.currentTimeMillis() - currentMS;//移动时间

if (Math.abs(moveX) > 50 && moveTime > 50) {
                    isTouching = true;
                    isfinish = true;

stopAutoRotation();
                }

break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (Math.abs(moveX) > 50 && moveTime > 50) {
                    isTouching = false;
                    isfinish = true;
                    if (isFling) {
                        isfinish = false;
                    } else {
                        isfinish = true;
                    }
                    restView();
                    resumeAutoRotation();
                }
                break;
        }
        return super.dispatchTouchEvent(event);
    }

/**
     * 停止自动加载
     */
    public void stopAutoRotation() {
        if (mHandler != null && mAutoRotation) {
            mHandler.removeMessages(CarrouselRotateHandler.mMsgWhat);
        }
    }

/**
     * 从新启动自动加载
     */
    public void resumeAutoRotation() {
        if (mHandler != null && mAutoRotation) {
            mHandler.sendEmptyMessageDelayed(CarrouselRotateHandler.mMsgWhat, 1000);
        }
    }

/**
     * 获取所有的view
     *
     * @return
     */
    public List<View> getViews() {
        return mCarrouselViews;
    }

/**
     * 获取角度
     *
     * @return
     */
    public float getAngle() {
        return mAngle;
    }

/**
     * 设置角度
     *
     * @param angle
     */
    public void setAngle(float angle) {
        this.mAngle = angle;
    }

/**
     * 获取距离
     *
     * @return
     */
    public float getDistance() {
        return mDistance;
    }

/**
     * 设置距离
     *
     * @param distance
     */
    public void setDistance(float distance) {
        this.mDistance = distance;
    }

/**
     * 获取半径
     *
     * @return
     */
    public float getR() {
        return mCarrouselR;
    }

/**
     * 获取选择是第几个item
     *
     * @return
     */
    public int getSelectItem() {
        return selectItem;
    }

/**
     * 设置选中方法
     *
     * @param selectItem
     */
    public void setSelectItem(int selectItem) {
        if (selectItem >= 0) {
            float angle = 0;
            if (getSelectItem() == 0) {
                if (selectItem == mCarrouselViews.size() - 1) {
                    angle = mAngle - (360 / viewCount);
                } else {
                    angle = mAngle + (360 / viewCount);
                }
            } else if (getSelectItem() == mCarrouselViews.size() - 1) {
                if (selectItem == 0) {
                    angle = mAngle + (360 / viewCount);
                } else {
                    angle = mAngle - (360 / viewCount);
                }
            } else {
                if (selectItem > getSelectItem()) {
                    angle = mAngle + (360 / viewCount);
                } else {
                    angle = mAngle - (360 / viewCount);
                }
            }

float resultAngle = 0;
            float part = 360 / viewCount;
            if (angle < 0) {
                part = -part;
            }
            //最小角度
            float minvalue = (int) (angle / part) * part;
            Log.e("minvalue", minvalue + "");
            //最大角度
            float maxvalue = (int) (angle / part) * part;

if (angle >= 0) {//分为是否小于0的情况
                if (angle - mLastAngle > 0) {
                    resultAngle = maxvalue;
                    Log.e("maxvalue", resultAngle + "");
                } else {
                    resultAngle = minvalue;
                    Log.e("maxvalue", resultAngle + "");
                }
            } else {
                if (angle - mLastAngle < 0) {
                    resultAngle = maxvalue;
                } else {
                    resultAngle = minvalue;
                }
            }

if (viewCount > 0) startAnimRotation(resultAngle, null);
        }
    }

/**
     * 设置半径
     *
     * @param r
     */
    public CarrouselLayout setR(float r) {
        this.mCarrouselR = r;
        mDistance = 2f * r;
        return this;
    }

/**
     * 选中回调接口实现
     *
     * @param mOnCarrouselItemSelectedListener
     */
    public void setOnCarrouselItemSelectedListener(OnCarrouselItemSelectedListener mOnCarrouselItemSelectedListener) {
        this.mOnCarrouselItemSelectedListener = mOnCarrouselItemSelectedListener;
    }

/**
     * 点击事件回调
     *
     * @param mOnCarrouselItemClickListener
     */
    public void setOnCarrouselItemClickListener(OnCarrouselItemClickListener mOnCarrouselItemClickListener) {
        this.mOnCarrouselItemClickListener = mOnCarrouselItemClickListener;
    }

/**
     * 设置是否自动切换
     *
     * @param autoRotation
     */
    public CarrouselLayout setAutoRotation(boolean autoRotation) {
        this.mAutoRotation = autoRotation;
        mHandler.setAutoRotation(autoRotation);
        return this;
    }

/**
     * 获取自动切换时间
     *
     * @return
     */
    public long getAutoRotationTime() {
        return mHandler.getmRotationTime();
    }

/**
     * 设置自动切换时间间隔
     *
     * @param autoRotationTime
     */
    public CarrouselLayout setAutoRotationTime(long autoRotationTime) {
        if (mHandler != null)
            mHandler.setmRotationTime(autoRotationTime);
        return this;
    }

/**
     * 是否自动切换
     *
     * @return
     */
    public boolean isAutoRotation() {
        return mAutoRotation;
    }

/**
     * 设置自动选择方向
     *
     * @param mCarrouselRotateDirection
     * @return
     */
    public CarrouselLayout setAutoScrollDirection(CarrouselRotateDirection mCarrouselRotateDirection) {
        if (mHandler != null)
            mHandler.setmRotateDirection(mCarrouselRotateDirection);
        return this;
    }

public void createXAnimation(int from, int to, boolean start) {
        if (xAnimation != null) if (xAnimation.isRunning() == true) xAnimation.cancel();
        xAnimation = ValueAnimator.ofInt(from, to);
        xAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mRotationX = (Integer) animation.getAnimatedValue();
                refreshLayout();
            }
        });
        xAnimation.setInterpolator(new LinearInterpolator());
        xAnimation.setDuration(2000);
        if (start) xAnimation.start();
    }

public ValueAnimator createZAnimation(int from, int to, boolean start) {
        if (zAnimation != null) if (zAnimation.isRunning() == true) zAnimation.cancel();
        zAnimation = ValueAnimator.ofInt(from, to);
        zAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mRotationZ = (Integer) animation.getAnimatedValue();
                refreshLayout();
            }
        });
        zAnimation.setInterpolator(new LinearInterpolator());
        zAnimation.setDuration(2000);
        if (start) zAnimation.start();
        return zAnimation;
    }

public CarrouselLayout setRotationX(int mRotationX) {
        this.mRotationX = mRotationX;
        return this;
    }

public CarrouselLayout setRotationZ(int mRotationZ) {
        this.mRotationZ = mRotationZ;
        return this;
    }

public float getRotationX() {
        return mRotationX;
    }

public int getRotationZ() {
        return mRotationZ;
    }

public ValueAnimator getRestAnimator() {
        return restAnimator;
    }

public ValueAnimator getAnimationR() {
        return mAnimationR;
    }

public void setAnimationZ(ValueAnimator zAnimation) {
        this.zAnimation = zAnimation;
    }

public ValueAnimator getAnimationZ() {
        return zAnimation;
    }

public void setAnimationX(ValueAnimator xAnimation) {
        this.xAnimation = xAnimation;
    }

public ValueAnimator getAnimationX() {
        return xAnimation;
    }

}

handler消息机制控制动态及旋转

package com.example.administrator.icome.carrousellayout;

import android.os.Handler;
import android.os.Message;

/**
 * 旋转木马自动旋转控制handler
 * Created by dalong on 2016/11/12.
 */

public abstract class CarrouselRotateHandler extends Handler {
    //消息what
    public static final int mMsgWhat = 1000;
    //是否旋转
    private boolean isAutoRotation;
    //旋转事件间隔
    private  long mRotationTime;
    //消息对象
    private Message message;
    //旋转方向
    private CarrouselRotateDirection  mRotateDirection;

public CarrouselRotateHandler(boolean isAutoRotation, int mRotationTime , int mRotateDirection) {
        this.isAutoRotation = isAutoRotation;
        this.mRotationTime = mRotationTime;
        this.mRotateDirection=mRotateDirection==0?CarrouselRotateDirection.clockwise:CarrouselRotateDirection.anticlockwise;
        message=createMessage();
        setAutoRotation(isAutoRotation);
    }

/**
     * 消息处理
     * @param msg
     */
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case mMsgWhat:
                //如果自动旋转
                if(isAutoRotation){
                    //旋转通知
                    onRotating(mRotateDirection);
                    //再次发送消息  循环
                    sendMessage();
                }
                break;
        }
    }

/**
     * 需要旋转通知方法
     */
    public abstract void onRotating(CarrouselRotateDirection mRotateDirection);

/**
     * 创建消息对象
     * @return
     */
    private Message createMessage(){
        Message message=new Message();
        message.what=mMsgWhat;
        return  message;
    }

/**
     * 发送消息
     */
    public void sendMessage(){
        //清除所有mMsgWhat的消息
        try {
            removeMessages(mMsgWhat);
        } catch (Exception e) {
        }
        message=createMessage();
        this.sendMessageDelayed(message,mRotationTime);

}

/**
     * 获取是否自动旋转
     * @return
     */
    public boolean isAutoRotation() {
        return isAutoRotation;
    }

/**
     * 设置是否自动旋转
     * @param autoRotation
     */
    public void setAutoRotation(boolean autoRotation) {
        isAutoRotation = autoRotation;
        if(autoRotation){//如果需要旋转
            sendMessage();
        }else{//不需要旋转  需要清除所有消息队列中的消息
            removeMessages(mMsgWhat);
        }
    }

/**
     * 获取旋转事件间隔
     * @return
     */
    public long getmRotationTime() {
        return mRotationTime;
    }

/**
     * 设置旋转事件间隔
     * @param mRotationTime
     */
    public void setmRotationTime(long mRotationTime) {
        this.mRotationTime = mRotationTime;
    }

/**
     * 获取旋转方向
     * @return
     */
    public CarrouselRotateDirection getmRotateDirection() {
        return mRotateDirection;
    }

/**
     * 设置旋转方向
     * @param mRotateDirection
     */
    public void setmRotateDirection(CarrouselRotateDirection mRotateDirection) {
        this.mRotateDirection = mRotateDirection;
    }

}

public class CarrouselLayout extends RelativeLayout {

    private Context mContext;
//自动旋转 默认不自动
private boolean mAutoRotation; //旋转间隔时间 默认设置为2秒
private int mRotationTime; //旋转木马旋转半径 圆的半径
private float mCarrouselR; //camera和旋转木马距离
private float mDistance = 2f * mCarrouselR; //旋转方向 分0顺时针和 1逆时针 俯视旋转木马看
private int mRotateDirection; //handler
private CarrouselRotateHandler mHandler; //手势处理
private GestureDetector mGestureDetector; //x旋转
private int mRotationX; //Z旋转
private int mRotationZ; //旋转的角度
private float mAngle = ; //旋转木马子view
private List<View> mCarrouselViews = new ArrayList<>(); //旋转木马子view的数量
private int viewCount; //半径扩散动画
private ValueAnimator mAnimationR; //记录最后的角度 用来记录上一次取消touch之后的角度
private float mLastAngle; //是否在触摸
private boolean isTouching; //旋转动画
private ValueAnimator restAnimator; //选中item
private int selectItem; //item选中回调接口
private OnCarrouselItemSelectedListener mOnCarrouselItemSelectedListener; //item点击回调接口
private OnCarrouselItemClickListener mOnCarrouselItemClickListener; //x轴旋转动画
private ValueAnimator xAnimation; //z轴旋转动画
private ValueAnimator zAnimation; private Boolean isfinish = true;//惯性动画是否结束
private boolean isFling; public CarrouselLayout(Context context) {
this(context, null);
} public CarrouselLayout(Context context, AttributeSet attrs) {
this(context, attrs, );
} public CarrouselLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
} private void init(Context context, AttributeSet attrs) {
this.mContext = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarrouselLayout);
mAutoRotation = typedArray.getBoolean(R.styleable.CarrouselLayout_autoRotation, false);
mRotationTime = typedArray.getInt(R.styleable.CarrouselLayout_rotationTime, );
mCarrouselR = typedArray.getDimension(R.styleable.CarrouselLayout_r, );
mRotateDirection = typedArray.getInt(R.styleable.CarrouselLayout_rotateDirection, );
typedArray.recycle();
mGestureDetector = new GestureDetector(context, getGestureDetectorController());
initHandler();
} /**
* 初始化handler对象
*/
private void initHandler() {
mHandler = new CarrouselRotateHandler(mAutoRotation, mRotationTime, mRotateDirection) {
@Override
public void onRotating(CarrouselRotateDirection rotateDirection) {//接受到需要旋转指令
try {
if (viewCount != ) {//判断自动滑动从那边开始
int perAngle = ;
switch (rotateDirection) {
case clockwise:
perAngle = 360 / viewCount;
break;
case anticlockwise:
perAngle = -360 / viewCount;
break;
}
if (mAngle == ) {
mAngle = 0f;
} if (isfinish)
startAnimRotation(mAngle + perAngle, null);
}
} catch (Exception e) {
e.printStackTrace();
} }
};
} private GestureDetector.SimpleOnGestureListener getGestureDetectorController() { return new GestureDetector.SimpleOnGestureListener() { @Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
isFling = true; if (e2.getX() - e1.getX() < ) { // 左滑
setAutoScrollDirection(CarrouselRotateDirection.clockwise);
} else {
setAutoScrollDirection(CarrouselRotateDirection.anticlockwise);
}
return true;
} @Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
isFling = false;
if (distanceX > ) { // 左滑
setAutoScrollDirection(CarrouselRotateDirection.clockwise);
} else {
setAutoScrollDirection(CarrouselRotateDirection.anticlockwise);
}
//转换成弧度
double radians = Math.toRadians(mRotationZ);
//Math.cos(radians) 返回对应的radians弧度的余弦值
mAngle += Math.cos(radians) * (distanceX / ) + Math.sin(radians) * (distanceY / );
// Log.e("mAngle", mAngle + "");
Log.e("mRotationZ", mRotationZ + "");
//初始化
refreshLayout();
return true;
} };
} /**
* 初始化 计算平均角度后各个子view的位置
*/
public void refreshLayout() {
for (int i = ; i < mCarrouselViews.size(); i++) {
double radians = mAngle + 180 - i * 360 / viewCount;
float x0 = (float) Math.sin(Math.toRadians(radians)) * mCarrouselR;
float y0 = (float) Math.cos(Math.toRadians(radians)) * mCarrouselR;
float scale0 = (mDistance - y0) / (mDistance + mCarrouselR);
mCarrouselViews.get(i).setScaleX(scale0 < 0.5f ? 0.5f : scale0);
mCarrouselViews.get(i).setScaleY(scale0 < 0.5f ? 0.5f : scale0);
if (mCarrouselViews.get(i) instanceof RelativeLayout) {
if (((RelativeLayout) mCarrouselViews.get(i)).getChildCount() >= ) {
if (((RelativeLayout) mCarrouselViews.get(i)).getChildAt() instanceof ImageView) {
int value = Float.valueOf((scale0 < 0.5f ? 0.5f : scale0) * ).intValue();
((ImageView) ((RelativeLayout) mCarrouselViews.get(i)).getChildAt()).setImageAlpha(value);
// ((ImageView)((RelativeLayout) mCarrouselViews.get(i)).getChildAt(0)).setAlpha(scale0);
}
// ((RelativeLayout) mCarrouselViews.get(i)).getChildAt(0).setAlpha(scale0);
if (((RelativeLayout) mCarrouselViews.get(i)).getChildAt() instanceof TextView) {
((RelativeLayout) mCarrouselViews.get(i)).getChildAt().setAlpha(scale0 < 0.5f ? 0.5f : scale0);
}
}
}
float rotationX_y = (float) Math.sin(Math.toRadians(mRotationX * Math.cos(Math.toRadians(radians)))) * mCarrouselR;
float rotationZ_y = -(float) Math.sin(Math.toRadians(-mRotationZ)) * x0;
float rotationZ_x = (((float) Math.cos(Math.toRadians(-mRotationZ)) * x0) - x0);
mCarrouselViews.get(i).setTranslationX(x0 + rotationZ_x);
mCarrouselViews.get(i).setTranslationY(rotationX_y + rotationZ_y);
}
List<View> arrayViewList = new ArrayList<>();
arrayViewList.clear();
for (int i = ; i < mCarrouselViews.size(); i++) {
arrayViewList.add(mCarrouselViews.get(i));
}
sortList(arrayViewList);
postInvalidate();
} /**
* 排序
* 對子View 排序,然后根据变化选中是否重绘,这样是为了实现view 在显示的时候来控制当前要显示的是哪三个view,可以改变排序看下效果
*
* @param list
*/
@SuppressWarnings("unchecked")
private <T> void sortList(List<View> list) {
@SuppressWarnings("rawtypes")
Comparator comparator = new SortComparator();
T[] array = list.toArray((T[]) new Object[list.size()]);
Arrays.sort(array, comparator);
int i = ;
ListIterator<T> it = (ListIterator<T>) list.listIterator();
while (it.hasNext()) {
it.next();
it.set(array[i++]);
}
for (int j = ; j < list.size(); j++) {
list.get(j).bringToFront();
}
} /**
* 筛选器
*/
private class SortComparator implements Comparator<View> {
@Override
public int compare(View o1, View o2) {
return (int) (1000 * o1.getScaleX() - 1000 * o2.getScaleX());
}
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
refreshLayout();
if (mAutoRotation) {
mHandler.sendEmptyMessage(CarrouselRotateHandler.mMsgWhat);
}
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
checkChildView();
startAnimationR();
}
} /**
* 旋转木马半径打开动画
*/
public void startAnimationR() {
startAnimationR(1f, mCarrouselR);
} /**
* 旋转木马半径动画
*
* @param isOpen 是否打开 否则关闭
*/
public void startAnimationR(boolean isOpen) {
if (isOpen) {
startAnimationR(1f, mCarrouselR);
} else {
startAnimationR(mCarrouselR, 1f);
}
} /**
* 半径扩散、收缩动画 根据设置半径来实现
*
* @param from
* @param to
*/
public void startAnimationR(float from, float to) {
mAnimationR = ValueAnimator.ofFloat(from, to);
mAnimationR.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mCarrouselR = (Float) valueAnimator.getAnimatedValue();
refreshLayout();
}
});
mAnimationR.setInterpolator(new DecelerateInterpolator());
mAnimationR.setDuration();
mAnimationR.start();
} public void checkChildView() {
//先清空views里边可能存在的view防止重复
for (int i = ; i < mCarrouselViews.size(); i++) {
mCarrouselViews.remove(i);
}
final int count = getChildCount(); //获取子View的个数
viewCount = count;
for (int i = ; i < count; i++) {
final View view = getChildAt(i); //获取指定的子view
final int position = i;
mCarrouselViews.add(view);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mOnCarrouselItemClickListener != null) {
mOnCarrouselItemClickListener.onItemClick(view, position);
}
}
}); } } /**
* 复位
*/
private void restView() {
if (viewCount == ) {
return;
}
float resultAngle = ;
//平均角度
float averageAngle = 360 / viewCount;
if (mAngle < ) {
averageAngle = -averageAngle;
}
float minvalue = (int) (mAngle / averageAngle) * averageAngle;//最小角度
float maxvalue = (int) (mAngle / averageAngle) * averageAngle + averageAngle;//最大角度
if (mAngle >= ) {//分为是否小于0的情况
if (mAngle - mLastAngle > ) {
resultAngle = maxvalue;
} else {
resultAngle = minvalue;
}
} else {
if (mAngle - mLastAngle < ) {
resultAngle = maxvalue;
} else {
resultAngle = minvalue;
}
}
startAnimRotation(resultAngle, null);
} /**
* 动画旋转
*
* @param resultAngle
* @param complete
*/
private void startAnimRotation(float resultAngle, final Runnable complete) {
if (mAngle == resultAngle) {
return;
}
if (!isfinish) {
restAnimator = ValueAnimator.ofFloat(mAngle, mAngle + (resultAngle - mAngle) * );
//设置旋转匀速插值器
restAnimator.setInterpolator(new DecelerateInterpolator());
restAnimator.setDuration();
} else {
restAnimator = ValueAnimator.ofFloat(mAngle, resultAngle);
//设置旋转匀速插值器
restAnimator.setInterpolator(new LinearInterpolator());
restAnimator.setDuration();
} restAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (isTouching == false) {
mAngle = (Float) animation.getAnimatedValue();
refreshLayout();
}
}
});
restAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) { } @Override
public void onAnimationEnd(Animator animation) {
if (isTouching == false) {
selectItem = calculateItem();
if (selectItem < ) {
selectItem = viewCount + selectItem;
}
if (mOnCarrouselItemSelectedListener != null) {
mOnCarrouselItemSelectedListener.selected(mCarrouselViews.get(selectItem), selectItem);
} if (isfinish == false) {
isfinish = true;
}
isFling = false;
}
} @Override
public void onAnimationCancel(Animator animation) { } @Override
public void onAnimationRepeat(Animator animation) { }
});
restAnimator.start();
} /**
* 通过角度计算是第几个item
*
* @return
*/
private int calculateItem() {
return (int) (mAngle / (360 / viewCount)) % viewCount;
} /**
* 触摸停止计时器
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return setCanAutoRotation(ev);
} /**
* 触摸方法
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
} /**
* 触摸时停止自动加载
*
* @param event
*/ float DownX = ;
float DownY = ;
long currentMS = ; float moveX;
float moveY;
long moveTime; public boolean setCanAutoRotation(MotionEvent event) { boolean result = mGestureDetector.onTouchEvent(event);
if (result) {
this.getParent().requestDisallowInterceptTouchEvent(true);//通知父控件勿拦截本控件
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (!result) {
this.getParent().requestDisallowInterceptTouchEvent(true);//通知父控件勿拦截本控件
}
} switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastAngle = mAngle;
DownX = event.getX();//float DownX
DownY = event.getY();//float DownY
currentMS = System.currentTimeMillis();//long currentMS 获取系统时间 break;
case MotionEvent.ACTION_MOVE:
moveX = event.getX() - DownX;//X轴距离
moveY = event.getY() - DownY;//Y轴距离
moveTime = System.currentTimeMillis() - currentMS;//移动时间 if (Math.abs(moveX) > 50 && moveTime > ) {
isTouching = true;
isfinish = true; stopAutoRotation();
} break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (Math.abs(moveX) > 50 && moveTime > ) {
isTouching = false;
isfinish = true;
if (isFling) {
isfinish = false;
} else {
isfinish = true;
}
restView();
resumeAutoRotation();
}
break;
}
return super.dispatchTouchEvent(event);
} /**
* 停止自动加载
*/
public void stopAutoRotation() {
if (mHandler != null && mAutoRotation) {
mHandler.removeMessages(CarrouselRotateHandler.mMsgWhat);
}
} /**
* 从新启动自动加载
*/
public void resumeAutoRotation() {
if (mHandler != null && mAutoRotation) {
mHandler.sendEmptyMessageDelayed(CarrouselRotateHandler.mMsgWhat, );
}
} /**
* 获取所有的view
*
* @return
*/
public List<View> getViews() {
return mCarrouselViews;
} /**
* 获取角度
*
* @return
*/
public float getAngle() {
return mAngle;
} /**
* 设置角度
*
* @param angle
*/
public void setAngle(float angle) {
this.mAngle = angle;
} /**
* 获取距离
*
* @return
*/
public float getDistance() {
return mDistance;
} /**
* 设置距离
*
* @param distance
*/
public void setDistance(float distance) {
this.mDistance = distance;
} /**
* 获取半径
*
* @return
*/
public float getR() {
return mCarrouselR;
} /**
* 获取选择是第几个item
*
* @return
*/
public int getSelectItem() {
return selectItem;
} /**
* 设置选中方法
*
* @param selectItem
*/
public void setSelectItem(int selectItem) {
if (selectItem >= ) {
float angle = ;
if (getSelectItem() == ) {
if (selectItem == mCarrouselViews.size() - ) {
angle = mAngle - (360 / viewCount);
} else {
angle = mAngle + (360 / viewCount);
}
} else if (getSelectItem() == mCarrouselViews.size() - ) {
if (selectItem == ) {
angle = mAngle + (360 / viewCount);
} else {
angle = mAngle - (360 / viewCount);
}
} else {
if (selectItem > getSelectItem()) {
angle = mAngle + (360 / viewCount);
} else {
angle = mAngle - (360 / viewCount);
}
} float resultAngle = ;
float part = 360 / viewCount;
if (angle < ) {
part = -part;
}
//最小角度
float minvalue = (int) (angle / part) * part;
Log.e("minvalue", minvalue + "");
//最大角度
float maxvalue = (int) (angle / part) * part; if (angle >= ) {//分为是否小于0的情况
if (angle - mLastAngle > ) {
resultAngle = maxvalue;
Log.e("maxvalue", resultAngle + "");
} else {
resultAngle = minvalue;
Log.e("maxvalue", resultAngle + "");
}
} else {
if (angle - mLastAngle < ) {
resultAngle = maxvalue;
} else {
resultAngle = minvalue;
}
} if (viewCount > ) startAnimRotation(resultAngle, null);
}
} /**
* 设置半径
*
* @param r
*/
public CarrouselLayout setR(float r) {
this.mCarrouselR = r;
mDistance = 2f * r;
return this;
} /**
* 选中回调接口实现
*
* @param mOnCarrouselItemSelectedListener
*/
public void setOnCarrouselItemSelectedListener(OnCarrouselItemSelectedListener mOnCarrouselItemSelectedListener) {
this.mOnCarrouselItemSelectedListener = mOnCarrouselItemSelectedListener;
} /**
* 点击事件回调
*
* @param mOnCarrouselItemClickListener
*/
public void setOnCarrouselItemClickListener(OnCarrouselItemClickListener mOnCarrouselItemClickListener) {
this.mOnCarrouselItemClickListener = mOnCarrouselItemClickListener;
} /**
* 设置是否自动切换
*
* @param autoRotation
*/
public CarrouselLayout setAutoRotation(boolean autoRotation) {
this.mAutoRotation = autoRotation;
mHandler.setAutoRotation(autoRotation);
return this;
} /**
* 获取自动切换时间
*
* @return
*/
public long getAutoRotationTime() {
return mHandler.getmRotationTime();
} /**
* 设置自动切换时间间隔
*
* @param autoRotationTime
*/
public CarrouselLayout setAutoRotationTime(long autoRotationTime) {
if (mHandler != null)
mHandler.setmRotationTime(autoRotationTime);
return this;
} /**
* 是否自动切换
*
* @return
*/
public boolean isAutoRotation() {
return mAutoRotation;
} /**
* 设置自动选择方向
*
* @param mCarrouselRotateDirection
* @return
*/
public CarrouselLayout setAutoScrollDirection(CarrouselRotateDirection mCarrouselRotateDirection) {
if (mHandler != null)
mHandler.setmRotateDirection(mCarrouselRotateDirection);
return this;
} public void createXAnimation(int from, int to, boolean start) {
if (xAnimation != null) if (xAnimation.isRunning() == true) xAnimation.cancel();
xAnimation = ValueAnimator.ofInt(from, to);
xAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRotationX = (Integer) animation.getAnimatedValue();
refreshLayout();
}
});
xAnimation.setInterpolator(new LinearInterpolator());
xAnimation.setDuration();
if (start) xAnimation.start();
} public ValueAnimator createZAnimation(int from, int to, boolean start) {
if (zAnimation != null) if (zAnimation.isRunning() == true) zAnimation.cancel();
zAnimation = ValueAnimator.ofInt(from, to);
zAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRotationZ = (Integer) animation.getAnimatedValue();
refreshLayout();
}
});
zAnimation.setInterpolator(new LinearInterpolator());
zAnimation.setDuration();
if (start) zAnimation.start();
return zAnimation;
} public CarrouselLayout setRotationX(int mRotationX) {
this.mRotationX = mRotationX;
return this;
} public CarrouselLayout setRotationZ(int mRotationZ) {
this.mRotationZ = mRotationZ;
return this;
} public float getRotationX() {
return mRotationX;
} public int getRotationZ() {
return mRotationZ;
} public ValueAnimator getRestAnimator() {
return restAnimator;
} public ValueAnimator getAnimationR() {
return mAnimationR;
} public void setAnimationZ(ValueAnimator zAnimation) {
this.zAnimation = zAnimation;
} public ValueAnimator getAnimationZ() {
return zAnimation;
} public void setAnimationX(ValueAnimator xAnimation) {
this.xAnimation = xAnimation;
} public ValueAnimator getAnimationX() {
return xAnimation;
} }

Android星球效果实现的更多相关文章

  1. Android动画效果之自定义ViewGroup添加布局动画

    前言: 前面几篇文章介绍了补间动画.逐帧动画.属性动画,大部分都是针对View来实现的动画,那么该如何为了一个ViewGroup添加动画呢?今天结合自定义ViewGroup来学习一下布局动画.本文将通 ...

  2. Android动画效果之Property Animation进阶(属性动画)

    前言: 前面初步认识了Android的Property Animation(属性动画)Android动画效果之初识Property Animation(属性动画)(三),并且利用属性动画简单了补间动画 ...

  3. Android动画效果之初识Property Animation(属性动画)

    前言: 前面两篇介绍了Android的Tween Animation(补间动画) Android动画效果之Tween Animation(补间动画).Frame Animation(逐帧动画)Andr ...

  4. Android动画效果之Frame Animation(逐帧动画)

    前言: 上一篇介绍了Android的Tween Animation(补间动画) Android动画效果之Tween Animation(补间动画),今天来总结下Android的另外一种动画Frame ...

  5. Android动画效果之Tween Animation(补间动画)

    前言: 最近公司项目下个版本迭代里面设计了很多动画效果,在以往的项目中开发中也会经常用到动画,所以在公司下个版本迭代开始之前,抽空总结一下Android动画.今天主要总结Tween Animation ...

  6. Android 抽屉效果的导航菜单实现

    Android 抽屉效果的导航菜单实现 抽屉效果的导航菜单 看了很多应用,觉得这种侧滑的抽屉效果的菜单很好. 不用切换到另一个页面,也不用去按菜单的硬件按钮,直接在界面上一个按钮点击,菜单就滑出来,而 ...

  7. Android Toast效果设置

    Android Toast效果设置 Toast是Android中用来显示显示信息的一种机制,和Dialog不一样的是,Toast是没有焦点的,而且Toast显示的时间有限,过一定的时间就会自动消失.总 ...

  8. Android Toast效果

    Android Toast效果是一种提醒方式,在程序中使用一些短小的信息通知用户,过一会儿会自动消失,实现如下: FirstActivity.java package org.elvalad.acti ...

  9. 十六、Android 滑动效果汇总

    Android 滑动效果入门篇(一)—— ViewFlipper Android 滑动效果入门篇(二)—— Gallery Android 滑动效果基础篇(三)—— Gallery仿图像集浏览 And ...

随机推荐

  1. 说说React组件的State

    说说React组件的State React的核心思想是组件化的思想,应用由组件搭建而成, 而组件中最重要的概念是State(状态). 正确定义State React把组件看成一个状态机.通过与用户的交 ...

  2. Mac OS Eclipse 调试快捷键不好使(失效)的情况

    Eclipse调试使用的F5  F6  F8一直都好用,结果一次调试后忽然不好使. 问题原因,尚未知晓. 解决办法,重启机器.

  3. 记一次 net 使用 data.oracleclient 使用错误OCIEnvCreate 失败, 返回代码为-1

    前提: 公司除了领导和开发人员具有管理员权限,其他人员使用的都是域账号. 过程: 应要求开发一个 winfrom项目,使用data.oracleclient  本地开发,调试无误,放到服务器共享域用户 ...

  4. python自动化工具之pywinauto(一个实例)结合pyuserinput

    以下是pywinauto使用指南.这个窗口句柄可以在Spy++中查看 (Microsoft Spy++(查看窗口句柄) 10.00.30319 官方最新绿色版) python自动化工具之pywinau ...

  5. 微信分享config:ok 但自定义内容无效

    一.问题 使用微信 JSSDK 分享,出现自定义内容无效 ,也就是分享出去的内容不是你配置的内容. 但在调试过程中发现 congfig 都是 ok 的 二.解决 检查config 配置是否正确 js ...

  6. 用canvas画一个等腰三角形

    上图是代码,注意,宽高只有在canvas标签内部设置宽高,绘制的路径显示才是正常的:效果如下:

  7. 【数据科学】Python数据可视化概述

    注:很早之前就打算专门写一篇与Python数据可视化相关的博客,对一些基本概念和常用技巧做一个小结.今天终于有时间来完成这个计划了! 0. Python中常用的可视化工具 Python在数据科学中的地 ...

  8. 权限控制和OAuth

    目录 1 权限控制是什么 1.1 ACL 1.2 RBAC 1.2.1 名词术语 1.2.2 RBAC定义 1.2.3 RBAC分类 1.2.3.1 RBAC0 1.2.3.2 RBAC1 1.2.3 ...

  9. 【原创+整理】简述何为调用约定,函数导出名以及extern C

    何为调用约定 调用约定指的是函数在调用时会按照不同规则,翻译成不同的汇编代码.这和参数的压栈顺序和栈的清理方式相关,也就是说不同的调用约定,这些方式会做相应改变.一般编译器是以默认的调用约定编译一份代 ...

  10. sql server 获取自增列下一个值或者获取指定表的主键值

    IDENT_CURRENT('TableName')为当前的最大标识值, IDENT_INCR('TableName')为设置的标识值增量, 两者相加即为下一个标识值 如: SELECT IDENT_ ...