Android自定义之QQ身边的人

Posted 大白龙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义之QQ身边的人相关的知识,希望对你有一定的参考价值。

概述

本文主要是自定义QQ的身边的人,之前有位仁兄写过,这里呢主要也参考了他的实现方法和思路来实现,但是他实现的效果还有很多细节和QQ的效果差距还是蛮多的,这里面对以加以修改及美化。实乃是高仿中的高仿。如果想看下之前大神写的可以直接点击这里 http://blog.csdn.net/mr_immortalz/article/details/51319354 ,废话不多说首先看下qq的效果界面如下:




我们实现的效果:



通过2张图可以看出来几乎是差不多了,个别小图标因为没有ui就没有去放置。因为gif图录制看起来不是很清楚,大家可以运行代码看看效果。下面就可以撸码了。

这里我们分成2个部分来实现这个效果。


一、扫描view(圆环(渐变),扫描动画,小人头等)

  1、绘制圆环(这里是多个圆环,从中间开始绘制,绘制的颜色和宽度及透明度都是需要注意的)。

  2、绘制扫描动画效果。

  3、绘制中间头像icon(图片边缘还有一个白色圆圈,细节很重要啊)。

  4、扫描上面的小人(默认分2种颜色,意思男女,如果你喜欢第三性别,可以多加一个。当切换的时候需要更换成对应的图片icon)。

   以上都是通过自定义来实现。

二、底部人物切换view(来回切换,左右透明度,按钮透明度等)

   1、这里就是通过recycleview来实现。(重写部分方法实现底部切换效果)。

注:底部背景是一个图片。

待优化效果:

   1、点击扫描界面的小人的时候,下面自动切换对应的位置。


我们已经把实现方法步骤话了,那么我们就开始撸码了.


第一步:1、自定义View的属性,首先在res/values/  下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。

 <declare-styleable name="RadarView">
        <attr name="mRingColor" format="color"/>
        <attr name="mScanBgColor" format="color"/>
        <attr name="mRingWidth" format="dimension"/>
        <attr name="mRingNum" format="integer"/>
        <attr name="mScanSpeed" format="integer"/>
        <attr name="mScanAngle" format="integer"/>
    </declare-styleable>


下面创建一个自定义类RadarView.class类(这里单纯包括圆环,扫描和中间icon)。

public class RadarView extends View 

    //圆环颜色
    public int mRingColor;
    //扫描背景色
    public int mScanBgColor;
    //圆环的看度
    public float mRingWidth;
    //圆环数量
    public int mRingNum;
    //扫描速度 越小越快  毫秒值
    public int mScanSpeed;
    //扫描角度
    private  int mScanAngle;
    //圆环画笔
    private  Paint mRingPaint;
    //中间图片
    private  Paint mCicleIconPaint;
    //扫描画笔
    private  Paint mScanPaint;
    //中间图片
    private Bitmap mCenterIcon;
    //宽
    private int mWidth;
    //高
    private int mHeight;
    //圆环比例
    private float mRingScale=1/13f;

    private SweepGradient mScanShader;
    //旋转需要的矩阵
    private Matrix matrix = new Matrix();

    private OnScanningListener mOnScanningListener;
    //是否开始回调
    private boolean startScan;
    //当前扫描的次数
    private int currentScanningCount;
    //当前扫描显示的item
    private int currentScanningItem;
    //最大扫描次数
    private int maxScanItemCount;
    private float currentScanAngle;
    public RadarView(Context context) 
        this(context,null);
    

    public RadarView(Context context, AttributeSet attrs) 
        this(context, attrs,0);
    

    public RadarView(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
        TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.RadarView);
        mRingColor=typedArray.getColor(R.styleable.RadarView_mRingColor, Color.parseColor("#3C8EAE"));
        mScanBgColor=typedArray.getColor(R.styleable.RadarView_mScanBgColor, Color.parseColor("#84B5CA"));
        mRingWidth=typedArray.getDimensionPixelSize(R.styleable.RadarView_mRingWidth, 1);
        mRingNum=typedArray.getInteger(R.styleable.RadarView_mRingNum, 6);
        mScanSpeed=typedArray.getColor(R.styleable.RadarView_mScanSpeed, 20);
        mScanAngle=typedArray.getColor(R.styleable.RadarView_mScanAngle, 5);
        typedArray.recycle();

        //中间图片
        mCenterIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.dabai);
        //设置多个圆环画笔
        mRingPaint=new Paint();
        mRingPaint.setColor(mRingColor);
        mRingPaint.setAntiAlias(true);
        mRingPaint.setStrokeWidth(mRingWidth);
        mRingPaint.setStyle(Paint.Style.STROKE);


        //设置中间图片画笔
        mCicleIconPaint=new Paint();
        mCicleIconPaint.setColor(Color.WHITE);
        mCicleIconPaint.setAntiAlias(true);


        //扫描画笔
        mScanPaint = new Paint();
        mScanPaint.setStyle(Paint.Style.FILL_AND_STROKE);

        //启动扫描
        post(mRunnable);
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        setMeasuredDimension(measureSize(widthMeasureSpec),measureSize(heightMeasureSpec));
    

    /**
     * 测量宽高  默认给400
     * @param measureSpec
     * @return
     */
    private int measureSize(int measureSpec) 
        int result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) 
            result = specSize;
         else 
            result = 400;
            if (specMode == MeasureSpec.AT_MOST) 
                result = Math.min(result, specSize);
            
        
        return result;
    

    @Override
    protected void onDraw(Canvas canvas) 
        super.onDraw(canvas);
        // 这里设置宽高  主要思想是因为是方形 所以取最小的作为宽高值
        mWidth=getMeasuredWidth();
        mHeight=getMeasuredHeight();
        mWidth = mHeight = Math.min(mWidth, mHeight);

        // 绘制圆环
        drawRing(canvas);
        // 绘制扫描
        drawScan(canvas);
        // 绘制中间icon
        drawCenterIcon(canvas);
    

    /**
     * 绘制中间icon
     * @param canvas
     */
    private void drawCenterIcon(Canvas canvas) 
        //这里以最中间的圆为区域设置这个中间图片 中间需要去除padding值
        float scale=0.8f;
        float radius=(mWidth-getPaddingLeft()-getPaddingRight())*mRingScale*scale;
        Rect rect=new Rect((int)(mWidth/2-radius),(int)(mHeight/2-radius),(int)(mWidth/2+radius),(int)(mHeight/2+radius));
        canvas.drawBitmap(mCenterIcon,null,rect,mCicleIconPaint);

        mCicleIconPaint.setColor(Color.WHITE);
        mCicleIconPaint.setStyle(Paint.Style.STROKE);
        mCicleIconPaint.setStrokeWidth(2);
        canvas.drawCircle(mWidth/2,mHeight/2,radius,mCicleIconPaint);
    

    /**
     * 绘制圆环
     * @param canvas
     */
    private void drawRing(Canvas canvas) 

        /**
         * 这里根据设置的一共所有的圆环数量来进行遍历绘制  中间就是空间的中心, 绘制的半径就是中心向两边延伸,每次延伸设定的宽度(去除padding)与比例相乘
         */
        for (int i=0;i<mRingNum;i++)
            mRingPaint.setAlpha(getAlpha(mRingNum, i));
            canvas.drawCircle(mWidth / 2, mHeight / 2,
                    (mWidth-getPaddingLeft()-getPaddingRight()) * (1+i)*mRingScale, mRingPaint);     // 绘制小圆
        
    

    /**
     * 绘制扫描
     * @param canvas
     */
    private void drawScan(Canvas canvas) 
        /**
         *   设置扫描渲染的shader  这里需要了解下这个类
         *   SweepGradient  扫描/梯度渲染
         *   public SweepGradient(float cx, float cy, int[] colors, float[] positions)
         *   cx	渲染中心点 x 坐标
         *   cy	渲染中心 y 点坐标
         *   colors	围绕中心渲染的颜色数组,至少要有两种颜色值
         *   positions	相对位置的颜色数组,可为null,  若为null,可为null,颜色沿渐变线均匀分布
         */
        mScanShader = new SweepGradient(mWidth / 2, mHeight / 2, new int[]Color.TRANSPARENT, mScanBgColor, null);
        //保存画布当前的状态
        canvas.save();
        mScanPaint.setShader(mScanShader);
        //canvas.concat可以理解成对matrix的变换应用到canvas上的所有对象
        canvas.concat(matrix);
        //绘制圆
        canvas.drawCircle(mWidth / 2, mHeight / 2, (mWidth-getPaddingLeft()-getPaddingRight()) * (mRingNum-1) * mRingScale, mScanPaint);
        //取出之前保存过的状态 和save成对出现 为了不影响其他部分的绘制
        canvas.restore();
    
    /**
     * 获取透明度  通过当前index占总共数量的count的比例来设置透明度
     * @param halfCount
     * @param index
     * @return
     */
    private int getAlpha(int halfCount, int index) 
        int MAX_ALPHA_VALUE = 255;
        int alpha= MAX_ALPHA_VALUE / halfCount * (halfCount - index);
        return index==0?0:alpha-25;
    

    /**
     * 实现扫描
     */
    private Runnable mRunnable = new Runnable() 
        @Override
        public void run() 
            matrix.postRotate(mScanAngle, getMeasuredWidth()/2,getMeasuredWidth()/2);
            invalidate();
            postDelayed(mRunnable, mScanSpeed);
            currentScanAngle = (currentScanAngle + mScanAngle) % 360;
            if (startScan && currentScanningCount <= (360 / mScanAngle)) 
                if (mOnScanningListener != null && currentScanningCount % mScanAngle == 0
                        && currentScanningItem < maxScanItemCount) 
                    mOnScanningListener.onScanning(currentScanningItem, currentScanAngle);
                    currentScanningItem++;
                 else if (mOnScanningListener != null && currentScanningItem == maxScanItemCount) 
                    mOnScanningListener.onScanSuccess();
                
                currentScanningCount++;
            
        
    ;

    /**
     * 开始扫描
     */
    public void startScan()
        this.startScan=true;
    

    /**
     * 实现接口回调
     * @param mOnScanningListener
     */
    public void setOnScanningListener(OnScanningListener mOnScanningListener)
        this.mOnScanningListener=mOnScanningListener;
    

    public interface  OnScanningListener 
        /**
         * 正在扫描(此时还没有扫描完毕)时回调
         * @param position
         * @param scanAngle
         */
        void onScanning(int position, float scanAngle);

        /**
         * 扫描成功时回调
         */
        void onScanSuccess();
    

    /**
     * 设置圆环数量
     * @param mRingNum
     */
    public void setMaxRingNum(int mRingNum) 
        this.mRingNum = mRingNum;
    

    /**
     * 设置圆环颜色
     * @param mRingColor
     */
    public void setRingColor(int mRingColor) 
        this.mRingColor = mRingColor;
    

    /**
     * 设置扫描颜色
     * @param mScanBgColor
     */
    public void setScanBgColor(int mScanBgColor) 
        this.mScanBgColor = mScanBgColor;
    

    /**
     * 设置圆环宽度
     * @param mRingWidth
     */
    public void setRingWidth(float mRingWidth) 
        this.mRingWidth = mRingWidth;
    

    /**
     * 设置圆环数量
     * @param mRingNum
     */
    public void setRingNum(int mRingNum) 
        this.mRingNum = mRingNum;
    

    /**
     * 设置扫描速度  毫秒
     * @param mScanSpeed
     */
    public void setScanSpeed(int mScanSpeed) 
        this.mScanSpeed = mScanSpeed;
    


    /**
     * 设置旋转角度 每次刷新旋转的角度
     * @param mScanAngle
     */
    public void setScanAngle(int mScanAngle) 
        this.mScanAngle = mScanAngle;
    

    /**
     * 设置圆环的半径与空间的宽度的比例
     * @param mRingScale
     */
    public void setRingScale(float mRingScale) 
        this.mRingScale = mRingScale;
    

    /**
     * 设置最大扫描item数量
     * @param maxScanItemCount
     */
    public void setMaxScanItemCount(int maxScanItemCount) 
        this.maxScanItemCount = maxScanItemCount;
    


上面我已经注释写的很详细了,也蛮简单所以就不废话了。上面这个类实现的效果是这样的:





下面就让我们实现扫描上面的小人。这里我们首先创建一个人对象:

public class People 
    //名字
    private String name;
    //年龄
    private String age;
    //头像id
    private int portraitId;
    //false为男,true为女
    private boolean sex;
    //距离
    private float distance;

    public int getPortraitId() 
        return portraitId;
    

    public void setPortraitId(int portraitId) 
        this.portraitId = portraitId;
    

    public String getAge() 
        return age;
    

    public void setAge(String age) 
        this.age = age;
    

    public float getDistance() 
        return distance;
    

    public void setDistance(float distance) 
        this.distance = distance;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public boolean getSex() 
        return sex;
    

    public void setSex(boolean sex) 
        this.sex = sex;
    



还需要自定义一个view,主要是实现男女默认小圆和真是小人图功能。这里我简称“小人头view”。

public class RadarCircleView extends View 
    //画笔
    private Paint mPaint;
    //图片
    private Bitmap mBitmap;
    //半径
    private float radius = dp2px(getContext(),7);
    //位置X
    private float disX;
    //位置Y
    private float disY;
    //旋转的角度
    private float angle;
    //根据远近距离的不同计算得到的应该占的半径比例
    private float proportion;
    public RadarCircleView(Context context) 
        this(context,null);
    

    public RadarCircleView(Context context, AttributeSet attrs) 
        this(context, attrs,0);
    

    public RadarCircleView(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
        init();
    

    private void init() 
        mPaint = new Paint();
        mPaint.setColor(Color.parseColor("#FF90A2"));
        mPaint.setAntiAlias(true);
    


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureSize(widthMeasureSpec), measureSize(heightMeasureSpec));
    

    private int measureSize(int measureSpec) 
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) 
            result = specSize;
         else 
            result = dp2px(getContext(),18);
            if (specMode == MeasureSpec.AT_MOST) 
                result = Math.min(result, specSize);
            
        
        return result;
    

    /**
     *  将dp值转换为px值
     * @param context
     * @param dpValue
     * @return
     */
    public  int dp2px(Context context, float dpValue) 
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    

    @Override
    protected void onDraw(Canvas canvas) 
        //画圆
        canvas.drawCircle(radius, radius, radius, mPaint);
        //如果mBitmap不为空再画小人图
        if (mBitmap != null) 
            canvas.drawBitmap(mBitmap, null, new Rect(0, 0, 2 * (int) radius, 2 * (int) radius), mPaint);
        
    

    /**
     * 设置画笔的颜色
     * @param resId
     */
    public void setPaintColor(int resId) 
        mPaint.setColor(resId);
        invalidate();
    

    /**
     * 设置真实小人icon id
     * @param resId
     */
    public void setPortraitIcon(int resId) 
        mBitmap = BitmapFactory.decodeResource(getResources(), resId);
        invalidate();
    

    /**
     * 清楚真实小人icon
     */
    public void clearPortaitIcon()
        mBitmap = null;
        invalidate();
    



    public float getProportion() 
        return proportion;
    

    public void setProportion(float proportion) 
        this.proportion = proportion;
    

    public float getAngle() 
        return angle;
    

    public void setAngle(float angle) 
        this.angle = angle;
    

    public float getDisX() 
        return disX;
    

    public void setDisX(float disX) 
        this.disX = disX;
    

    public float getDisY() 
        return disY;
    

    public void setDisY(float disY) 
        this.disY = disY;
    



再自定义一个viewGrop主要的作用就是为了实现上面的扫描加小人的功能,上面实现的RadarView就是这个viewGrop的一个子view ,然后我们在这个viewGrop上通过数据在上面绘制很多个“小人头view”,就达到了我们需要实现的效果。自定义的viewGrop如下代码注释很详细就多说了。

public class RadarViewLayout   extends ViewGroup implements RadarView.OnScanningListener 
    //控件的宽
    private int  mWidth;
    //控件的高
    private int  mHeight;
    //数据源
    private SparseArray<People> mDatas;
    //记录展示的item所在的扫描位置角度
    private SparseArray<Float> scanAngleList = new SparseArray<>();
    //最小距离的item所在数据源中的位置
    private int minItemPosition;
    //当前展示的item
    private RadarCircleView currentShowChild;
    //最小距离的item
    private RadarCircleView minShowChild;
    public RadarViewLayout(Context context) 
        this(context,null);
    

    public RadarViewLayout(Context context, AttributeSet attrs) 
        this(context, attrs,0);
    

    public RadarViewLayout(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        setMeasuredDimension(measureSize(widthMeasureSpec), measureSize(heightMeasureSpec));
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        mWidth = mHeight = Math.min(mWidth, mHeight);
        //测量每个children
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        for (int i = 0; i < getChildCount(); i++) 
            View child = getChildAt(i);
            if (child.getId() == R.id.radarView) 
                //为雷达扫描图设置需要的属性
                ((RadarView) child).setOnScanningListener(this);
                //考虑到数据没有添加前扫描图在扫描,但是不会开始为CircleView布局
                if (mDatas != null && mDatas.size() > 0) 
                    ((RadarView) child).setMaxScanItemCount(mDatas.size());
                    ((RadarView) child).startScan();
                
                continue;
            
        
    

    /**
     * 测量宽高  默认给400
     * @param measureSpec
     * @return
     */
    private int measureSize(int measureSpec) 
        int result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) 
            result = specSize;
         else 
            result = 400;
            if (specMode == MeasureSpec.AT_MOST) 
                result = Math.min(result, specSize);
            
        
        return result;
    


    @Override
    protected void onLayout(boolean changed,int l, int t, int r, int b) 
        int childCount = getChildCount();
        /**
         * 首先放置扫描view的位置
         */
        View view = findViewById(R.id.radarView);
        if (view != null) 
            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
        
        //放置雷达图中需要展示的item圆点
        for (int i = 0; i < childCount; i++) 
            final int j = i;
            final View child = getChildAt(i);
            //如果是扫描view直接忽略
            if (child.getId() == R.id.radarView) 
                continue;
            
            //设置CircleView小圆点的坐标信息
            //坐标 = 旋转角度 * 半径 * 根据远近距离的不同计算得到的应该占的半径比例
            if(child instanceof RadarCircleView)
                RadarCircleView radarCircleView=(RadarCircleView) child;
                radarCircleView.setDisX((float) Math.cos(Math.toRadians(scanAngleList.get(i - 1) - 5))
                        * radarCircleView.getProportion() * mWidth / 2);
                radarCircleView.setDisY((float) Math.sin(Math.toRadians(scanAngleList.get(i - 1) - 5))
                        * radarCircleView.getProportion() * mWidth / 2);

                //如果扫描角度记录SparseArray中的对应的item的值为0,
                // 说明还没有扫描到该item,跳过对该item的layout
                //(scanAngleList设置数据时全部设置的value=0,
                // 当onScanning时,value设置的值始终不会0,具体可以看onScanning中的实现)
                if (scanAngleList.get(i - 1) == 0) 
                    continue;
                
                //放置Circle小圆点
                child.layout((int)radarCircleView.getDisX() + mWidth / 2, (int) radarCircleView.getDisY() + mHeight / 2,
                        (int) radarCircleView.getDisX() + child.getMeasuredWidth() + mWidth / 2,
                        (int)radarCircleView.getDisY() + child.getMeasuredHeight() + mHeight / 2);
                //设置点击事件
                child.setOnClickListener(new OnClickListener() 
                    @Override
                    public void onClick(View v) 
                        resetAnim(currentShowChild);
                        currentShowChild = (RadarCircleView) child;
                        //因为雷达图是childAt(0),所以这里需要作-1才是正确的Circle
                        startAnim(currentShowChild, j - 1);
                        if (mOnRadarClickListener != null) 
                            mOnRadarClickListener.onRadarItemClick(j - 1);
                        
                    
                );
            



        
    

    /**
     * 设置数据
     *
     * @param mDatas
     */
    public void setDatas(SparseArray<People> mDatas) 
        this.mDatas = mDatas;
        float min = Float.MAX_VALUE;
        float max = Float.MIN_VALUE;
        //找到距离的最大值,最小值对应的minItemPosition
        for (int j = 0; j <  mDatas.size(); j++) 
            People item = mDatas.get(j);
            if (item.getDistance() < min) 
                min = item.getDistance();
                minItemPosition = j;
            
            if (item.getDistance() > max) 
                max = item.getDistance();
            
            scanAngleList.put(j, 0f);
        
        //根据数据源信息动态添加CircleView
        for (int i = 0; i < mDatas.size(); i++) 
            RadarCircleView circleView = new RadarCircleView(getContext());
            if (mDatas.get(i).getSex()) 
                circleView.setPaintColor(Color.parseColor("#FF90A2"));
             else 
                circleView.setPaintColor(Color.parseColor("#43D5FF"));
            
            //根据远近距离的不同计算得到的应该占的半径比例 0.312-0.832
            circleView.setProportion((mDatas.get(i).getDistance() / max + 0.6f) * 0.52f);
            if (minItemPosition == i) 
                minShowChild = circleView;
            
            addView(circleView);
        
    
    /**
     * 雷达图没有扫描完毕时回调
     *
     * @param position
     * @param scanAngle
     */
    @Override
    public void onScanning(int position, float scanAngle) 
        if (scanAngle == 0) 
            scanAngleList.put(position, 1f);
         else 
            scanAngleList.put(position, scanAngle);
        
        requestLayout();
    
    /**
     * 雷达图扫描完毕时回调
     */
    @Override
    public void onScanSuccess() 
        resetAnim(currentShowChild);
        currentShowChild = minShowChild;
        startAnim(currentShowChild, minItemPosition);
    

    /**
     * 恢复CircleView小圆点原大小
     *
     * @param object
     */
    private void resetAnim(RadarCircleView object) 
        if (object != null) 
            object.clearPortaitIcon();
            ObjectAnimator.ofFloat(object, "scaleX", 1f).setDuration(300).start();
            ObjectAnimator.ofFloat(object, "scaleY", 1f).setDuration(300).start();
        

    

    /**
     * 放大CircleView小圆点大小
     *
     * @param object
     * @param position
     */
    private void startAnim(RadarCircleView object, int position) 
        if (object != null) 
            object.setPortraitIcon(mDatas.get(position).getPortraitId());
            ObjectAnimator.ofFloat(object, "scaleX", 2f).setDuration(300).start();
            ObjectAnimator.ofFloat(object, "scaleY", 2f).setDuration(300).start();
        
    

    /**
     * 根据position,放大指定的CircleView小圆点
     * @param position
     */
    public void setCurrentShowItem(int position) 
        RadarCircleView child = (RadarCircleView) getChildAt(position + 1);
        resetAnim(currentShowChild);
        currentShowChild = child;
        startAnim(currentShowChild, position);
    


    /**
     * 雷达图中点击监听CircleView小圆点回调接口
     */
    public interface OnRadarClickListener 
        void onRadarItemClick(int position);
    
    //雷达图中点击监听CircleView小圆点回调接口
    private OnRadarClickListener mOnRadarClickListener;

    public void setOnRadarClickListener(OnRadarClickListener mOnRadarClickListener) 
        this.mOnRadarClickListener = mOnRadarClickListener;
    

通过上面的实现我们的效果就有了变化了。看看我们的现在的成果:



下面我们该实现底部的效果了,之前大神实现的是通过viewpager实现的,这里我之前也有实现过这里就不多讲解viewpager实现原理这里给个链接大家可以单独去看看:

https://github.com/dalong982242260/SlidingBallViewPager  这里我们使用recycleview实现这个效果。首先我们重写下RecycleView。

public class GalleryRecyclerView extends RecyclerView 

    private final static int MINIMUM_SCROLL_EVENT_OFFSET_MS = 20;

    private boolean userScrolling = false;
    private boolean mScrolling = false;
    private int scrollState = SCROLL_STATE_IDLE;
    //最后滑动时间
    private long lastScrollTime = 0;
    //handler
    private Handler mHandler = new Handler();
    //是否支持缩放
    private boolean scaleViews = false;
    //是否支持透明度
    private boolean alphaViews = false;
    //方向  默认水平
    private Orientation orientation = Orientation.HORIZONTAL;

    private ChildViewMetrics childViewMetrics;
    //选中回调
    private OnViewSelectedListener listener;
    // 选中的位置position
    private int selectedPosition;
    //recycleview   LinearLayoutManager
    private LinearLayoutManager mLinearLayoutManager;

    private TouchDownListem listem;
    //缩放基数
    private float baseScale=0.7f;
    //缩放透明度
    private float baseAlpha=0.7f;

    public GalleryRecyclerView(Context context) 
        this(context, null);
    

    public GalleryRecyclerView(Context context, AttributeSet attrs) 
        this(context, attrs, 0);
    

    public GalleryRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
        init();
    

    private void init() 
        setHasFixedSize(true);
        setOrientation(orientation);
        enableSnapping();
    

    private boolean scrolling;

    /**
     * 获取当前位置position
     * @return
     */
    public int getCurrentPosition()
        return selectedPosition;
    
    @Override
    public void onChildAttachedToWindow(View child) 
        super.onChildAttachedToWindow(child);

        if (!scrolling && scrollState == SCROLL_STATE_IDLE) 
            scrolling = true;
            scrollToView(getCenterView());
            updateViews();
        
    

    private void enableSnapping() 
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() 
            @Override
            public void onGlobalLayout() 
                getViewTreeObserver().removeGlobalOnLayoutListener(this);
            
        );

        addOnScrollListener(new OnScrollListener() 
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) 
                updateViews();
                super.onScrolled(recyclerView, dx, dy);
            

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) 
                super.onScrollStateChanged(recyclerView, newState);

                /** if scroll is caused by a touch (scroll touch, not any touch) **/
                if (newState == SCROLL_STATE_TOUCH_SCROLL) 
                    /** if scroll was initiated already, it would probably be a tap **/
                    /** if scroll was not initiated before, this is probably a user scrolling **/
                    if (!mScrolling) 
                        userScrolling = true;
                    
                 else if (newState == SCROLL_STATE_IDLE) 
                    /** if user is the one scrolling, snap to the view closest to center **/
                    if (userScrolling) 
                        scrollToView(getCenterView());
                    

                    userScrolling = false;
                    mScrolling = false;

                    /** if idle, always check location and correct it if necessary, this is just an extra check **/
                    if (getCenterView() != null && getPercentageFromCenter(getCenterView()) > 0) 
                        scrollToView(getCenterView());
                    

                    /** if idle, notify listeners of new selected view **/
                    notifyListener();
                 else if (newState == SCROLL_STATE_FLING) 
                    mScrolling = true;
                

                scrollState = newState;
            
        );
    

    /**
     * 通知回调并设置当前选中位置
     */
    private void notifyListener() 
        View view = getCenterView();
        int position = getChildAdapterPosition(view);
        /** if there is a listener and the index is not the same as the currently selected position, notify listener **/
        if (listener != null && position != selectedPosition) 
            listener.onSelected(view, position);
        
        selectedPosition = position;
    

    /**
     * 设置方向 水平 or 竖直
     * @param orientation LinearLayoutManager.HORIZONTAL or LinearLayoutManager.VERTICAL
     */
    public void setOrientation(Orientation orientation) 
        this.orientation = orientation;
        childViewMetrics = new ChildViewMetrics(orientation);
        mLinearLayoutManager=new LinearLayoutManager(getContext(), orientation.intValue(), false);
        setLayoutManager(mLinearLayoutManager);
    

    /**
     * 设置选择position
     * @param position
     */
    public void setSelectPosition(int position)
        mLinearLayoutManager.scrollToPositionWithOffset(position,0);
    


    /**
     * 设置选中回调接口
     * @param listener the OnViewSelectedListener
     */
    public void setOnViewSelectedListener(OnViewSelectedListener listener) 
        this.listener = listener;
    

    /**
     * 设置两边是否可以缩放
     * @param enabled
     */
    public void setCanScale(boolean enabled) 
        this.scaleViews = enabled;
    

    /**
     * 设置两边的透明度是否支持
     * @param enabled
     */
    public void setCanAlpha(boolean enabled) 
        this.alphaViews = enabled;
    


    /**
     * 设置基数缩放值
     * @param baseScale
     */
    public void setBaseScale(float baseScale) 
        this.baseScale = 1f-baseScale;
    

    /**
     * 设置基数透明度
     * @param baseAlpha
     */
    public void setBaseAlpha(float baseAlpha) 
        this.baseAlpha = 1f-baseAlpha;
    

    /**
     * 更新views
     */
    private void updateViews() 
        for (int i = 0; i < getChildCount(); i++) 
            View child = getChildAt(i);
            setMarginsForChild(child);
            float percentage = getPercentageFromCenter(child);
            float scale = 1f - (baseScale * percentage);
            float alpha = 1f - (baseAlpha * percentage);
            //设置缩放
            if (scaleViews) 
                child.setScaleX(scale);
                child.setScaleY(scale);
            
            //设置透明度
            if(alphaViews)
                child.setAlpha(alpha);
            
            View view=child.findViewById(R.id.item_btn);
            if(view!=null)
                view.setAlpha(1f-(percentage)/(1-0.5625f));
            
        
    

    /**
     *  Adds the margins to a childView so a view will still center even if it's only a single child
     * @param child childView to set margins for
     */
    private void setMarginsForChild(View child) 
        int lastItemIndex = getLayoutManager().getItemCount() - 1;
        int childIndex = getChildAdapterPosition(child);

        int startMargin = 0;
        int endMargin = 0;
        int topMargin = 0;
        int bottomMargin = 0;

        if (orientation == Orientation.VERTICAL) 
            topMargin = childIndex == 0 ? getCenterLocation() : 0;
            bottomMargin = childIndex == lastItemIndex ? getCenterLocation() : 0;
         else 
            startMargin = childIndex == 0 ? getCenterLocation() : 0;
            endMargin = childIndex == lastItemIndex ? getCenterLocation() : 0;
        

        /** if sdk minimum level is 17, set RTL margins **/
        if (orientation == Orientation.HORIZONTAL && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) 
            ((ViewGroup.MarginLayoutParams) child.getLayoutParams()).setMarginStart(startMargin);
            ((ViewGroup.MarginLayoutParams) child.getLayoutParams()).setMarginEnd(endMargin);
        

        /** If layout direction is RTL, swap the margins  **/
        if (ViewCompat.getLayoutDirection(child) == ViewCompat.LAYOUT_DIRECTION_RTL)
            ((ViewGroup.MarginLayoutParams) child.getLayoutParams()).setMargins(endMargin, topMargin, startMargin, bottomMargin);
        else 
            ((ViewGroup.MarginLayoutParams) child.getLayoutParams()).setMargins(startMargin, topMargin, endMargin, bottomMargin);
        

        /** if sdk minimum level is 18, check if view isn't undergoing a layout pass (this improves the feel of the view by a lot) **/
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) 
            if (!child.isInLayout())
                child.requestLayout();
        
    

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) 
        long currentTime = System.currentTimeMillis();

        /** if touch events are being spammed, this is due to user scrolling right after a tap,
         * so set userScrolling to true **/
        if (mScrolling && scrollState == SCROLL_STATE_TOUCH_SCROLL) 
            if ((currentTime - lastScrollTime) < MINIMUM_SCROLL_EVENT_OFFSET_MS) 
                userScrolling = true;
            
        

        lastScrollTime = currentTime;

        int location = orientation == Orientation.VERTICAL ? (int)event.getY() : (int)event.getX();

        View targetView = getChildClosestToLocation(location);

        if (!userScrolling) 
            if (event.getAction() == MotionEvent.ACTION_UP) 
                if (targetView != getCenterView()) 
                    scrollToView(targetView);
                    return true;
                
            
        

        return super.dispatchTouchEvent(event);
    

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) 
        if(event.getAction()== MotionEvent.ACTION_DOWN)
            if(listem!=null)
                listem.onTouchDown();
        
        int location = orientation == Orientation.VERTICAL ? (int)event.getY() : (int)event.getX();
        View targetView = getChildClosestToLocation(location);
        if (targetView != getCenterView()) 
            return true;
        
        return super.onInterceptTouchEvent(event);
    

    @Override
    public boolean onTouchEvent(MotionEvent e) 
        if(e.getAction()== MotionEvent.ACTION_DOWN)
            if(listem!=null)
                listem.onTouchDown();
        
        return super.onTouchEvent(e);
    


    public void setTouchDownlistem(TouchDownListem listem)
        this.listem=listem;
    
    public interface TouchDownListem
        void onTouchDown();
    
    @Override
    public void scrollToPosition(int position) 
        childViewMetrics.size(getChildAt(0));
        smoothScrollBy(childViewMetrics.size(getChildAt(0)) * position);
    

    private View getChildClosestToLocation(int location) 
        if (getChildCount() <= 0)
            return null;

        int closestPos = 9999;
        View closestChild = null;

        for (int i = 0; i < getChildCount(); i++) 
            View child = getChildAt(i);

            int childCenterLocation = (int) childViewMetrics.center(child);
            int distance = childCenterLocation - location;

            /** if child center is closer than previous closest, set it as closest child  **/
            if (Math.abs(distance) < Math.abs(closestPos)) 
                closestPos = distance;
                closestChild = child;
            
        

        return closestChild;
    

    /**
     * Check if the view is correctly centered (allow for 10px offset)
     * @param child the child view
     * @return true if correctly centered
     */
    private boolean isChildCorrectlyCentered(View child) 
        int childPosition = (int)childViewMetrics.center(child);
        return childPosition > (getCenterLocation() - 10) && childPosition < (getCenterLocation() + 10);
    

    /**
     * 获取中间的view
     * @return
     */
    public View getCenterView() 
        return getChildClosestToLocation(getCenterLocation());
    

    /**
     * 滚动指定view
     * @param child
     */
    private void scrollToView(View child) 
        if (child == null)
            return;

        stopScroll();

        int scrollDistance = getScrollDistance(child);

        if (scrollDistance != 0)
            smoothScrollBy(scrollDistance);
    

    /**
     * 获取需要滚动的距离
     * @param child
     * @return
     */
    private int getScrollDistance(View child) 
        int childCenterLocation = (int) childViewMetrics.center(child);
        return childCenterLocation - getCenterLocation();
    

    private float getPercentageFromCenter(View child) 
        float center = getCenterLocation();
        float childCenter = childViewMetrics.center(child);

        float offSet = Math.max(center, childCenter) - Math.min(center, childCenter);
        float maxOffset = (center + childViewMetrics.size(child));

        return (offSet / maxOffset);
    

    /**
     * 获取中间位置
     * @return
     */
    private int getCenterLocation() 
        if (orientation == Orientation.VERTICAL)
            return getMeasuredHeight() / 2;

        return getMeasuredWidth() / 2;
    

    public void smoothScrollBy(int distance) 
        if (orientation == Orientation.VERTICAL) 
            super.smoothScrollBy(0, distance);
            return;
        

        super.smoothScrollBy(distance, 0);
    

    public void scrollBy(int distance) 
        if (orientation == Orientation.VERTICAL) 
            super.scrollBy(0, distance);
            return;
        

        super.scrollBy(distance, 0);
    

    private void scrollTo(int position) 
        int currentScroll = getScrollOffset();
        scrollBy(position - currentScroll);
    

    public int getScrollOffset() 
        if (orientation == Orientation.VERTICAL)
            return computeVerticalScrollOffset();

        return computeHorizontalScrollOffset();
    

    @Override
    protected void onDetachedFromWindow() 
        super.onDetachedFromWindow();
        mHandler.removeCallbacksAndMessages(null);
    

    /**
     * 绘制一个中间view
     * @param canvas
     */
    @Override
    protected void dispatchDraw(Canvas canvas) 
        super.dispatchDraw(canvas);
    


    private static class ChildViewMetrics 
        private Orientation orientation;

        public ChildViewMetrics(Orientation orientation) 
            this.orientation = orientation;
        

        public int size(View view) 
            if (orientation == Orientation.VERTICAL)
                return view.getHeight();

            return view.getWidth();
        

        public float location(View view) 
            if (orientation == Orientation.VERTICAL)
                return view.getY();

            return view.getX();
        

        public float center(View view) 
            return location(view) + (size(view) / 2);
        
    

    public enum Orientation 
        HORIZONTAL(LinearLayout.HORIZONTAL),
        VERTICAL(LinearLayout.VERTICAL);

        int value;

        Orientation(int value) 
            this.value = value;
        

        public int intValue() 
            return value;
        
    

    /**
     * 中间view选中接口
     */
    public interface OnViewSelectedListener 
        void onSelected(View view, int position);
    


再看看我们的Mainactivity

public class MainActivity extends AppCompatActivity 
    private int[] mImgs = R.drawable.len, R.drawable.leo, R.drawable.lep,
            R.drawable.leq, R.drawable.ler, R.drawable.les, R.drawable.mln, R.drawable.mmz, R.drawable.mna,
            R.drawable.mnj, R.drawable.leo, R.drawable.leq;
    private String[] mNames = "橘子", "花生", "菠菜", "萝卜", "豆角", "西红柿", "香蕉", "苹果",
            "小麦","大米","玉米","白菜";
    private SparseArray<People> mDatas = new SparseArray<>();
    private List<People> mlist = new ArrayList<>();
    private RadarViewLayout mRadarViewLayout;
    private GalleryRecyclerView mGalleryRecyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    

    private void initView() 
        mRadarViewLayout = (RadarViewLayout) findViewById(R.id.radarViewLayout);
        mGalleryRecyclerView = (GalleryRecyclerView) findViewById(R.id.gallery);
        new Handler().postDelayed(new Runnable() 
            @Override
            public void run() 
                initData();
                mRadarViewLayout.setDatas(mDatas);
                mRadarViewLayout.setCurrentShowItem(0);

            
        , 1000);
        mGalleryRecyclerView.setCanAlpha(true);
        mGalleryRecyclerView.setCanScale(true);
        mGalleryRecyclerView.setBaseScale(0.25f);
        mGalleryRecyclerView.setBaseAlpha(0.1f);
        mGalleryRecyclerView.setAdapter(new CommonAdapter<People>(this, R.layout.item_gallery, mlist) 
            @Override
            public void convert(ViewHolder holder, final People s, int position) 
                holder.setText(R.id.name, s.getName());
                holder.setText(R.id.tv_distance, s.getAge()+" "+s.getDistance()+"km");
                holder.setImageResource(R.id.profile_image,s.getPortraitId());
                holder.getView(R.id.item_btn).setOnClickListener(new View.OnClickListener() 
                    @Override
                    public void onClick(View v) 
                        Toast.makeText(mContext, s.getName(), Toast.LENGTH_SHORT).show();
                    
                );
            
        );
        mGalleryRecyclerView.setOnViewSelectedListener(new GalleryRecyclerView.OnViewSelectedListener() 
            @Override
            public void onSelected(View view, final int position) 
                mRadarViewLayout.setCurrentShowItem(position);
            
        );

        mRadarViewLayout.setOnRadarClickListener(new RadarViewLayout.OnRadarClickListener() 
            @Override
            public void onRadarItemClick(final int position) 

                //待完善
            
        );
    


    private void initData() 
        mlist.clear();
        mDatas.clear();
        for (int i = 0; i < mImgs.length; i++) 
            People info = new People();
            info.setPortraitId(mImgs[i]);
            info.setAge(((int) Math.random() * 25 + 16) + "岁");
            info.setName(mNames[i]);
            info.setSex(i % 3 == 0 ? false : true);
            info.setDistance(Math.round((Math.random() * 10) * 100) / 100);
            mDatas.put(i, info);
            mlist.add(info);
        
    



大功造成!!!

看看效果吧!



这里附上github链接:https://github.com/dalong982242260/QQNearbyPeople


以上是关于Android自定义之QQ身边的人的主要内容,如果未能解决你的问题,请参考以下文章

Android自定义view之圆形进度条

Android 两种方式实现类似水波扩散效果

android中的自定义,禁用和空白地图

Android之QQ登录界面

android 自定义view之侧滑效果

自定义View之文字图形图片的阴影