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;
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身边的人的主要内容,如果未能解决你的问题,请参考以下文章