Android 每周一个小*之 学习仿网易云广场歌单的效果

/**

  • 用矩阵的方法,来定义一个点是否位于一个区域内

*/

private boolean isPointInView(View view, float[] points) {

// 像ViewGroup那样,先对齐一下Left和Top

points[0] -= view.getLeft();

points[1] -= view.getTop();

// 获取View所对应的矩阵

Matrix matrix = view.getMatrix();

// 如果矩阵有应用过变换

if (!matrix.isIdentity()) {

// 反转矩阵

matrix.invert(matrix);

// 映射坐标点

matrix.mapPoints(points);

}

//判断坐标点是否在view范围内

return points[0] >= 0 && points[1] >= 0 && points[0] < view.getWidth() && points[1] < view.getHeight();

}

学会了,学会了(抱拳)

2、关于层级交换的方法


关于层级的交换,其实就是把两个View从ViewGroup取出来,然后交换顺序,又放回去。

对应的是detachViewFromParent()attachViewToParent()方法

我们只需要知道,想要交换的两个View在ViewGroup的层级顺序是什么就行了。

其中 indexofChild(View v)可以知道它所处的层级,它越大,说明它越靠上,最大值就是 childCount-1,这个时候它是在最上层的 。

/**

  • 使用attachViewToParent和detachAllViewsFromParent来交换两个Index的层级

*/

public void exchangeOrder(int fromIndex, int toIndex) {

if (fromIndex == toIndex) {

return;

}

View fromChild = getChildAt(fromIndex);

View toChild = getChildAt(toIndex);

//分开这些View

detachViewFromParent(fromChild);

detachViewFromParent(toChild);

//交换两个View,本质就是交换它们的index

attachViewToParent(fromChild, toIndex > getChildCount() ? getChildCount() : toIndex, fromChild.getLayoutParams());

attachViewToParent(toChild, fromIndex > getChildCount() ? getChildCount() : fromIndex, toChild.getLayoutParams());

invalidate();

}

3、关于LayoutParams


LayoutParams是用来协助Viewgoup的,它可以给子View定义一些属性。而且也可以支持margin。

我们要给子View定义 :

  • 从哪里来(from)

  • 到哪里去(to)

  • 透明度(alpha)

  • 缩放值(scale)

所以我们要这样子重写:

/**

  • 这里要自己写一个ViewGroup的LayoutParams来记录 scale、alpha、from、to

*/

class RikkaLayoutParams extends MarginLayoutParams {

float scale = 0f;

float alpha = 0f;

int from;

int to;

…(getter and setter)

public RikkaLayoutParams(Context c, AttributeSet attrs) {

super(c, attrs);

}

public RikkaLayoutParams(int width, int height) {

super(width, height);

}

public RikkaLayoutParams(LayoutParams source) {

super(source);

}

}

/**

  • 要支持margin,所以要重写generate方法

*/

@Override

public LayoutParams generateLayoutParams(AttributeSet attrs) {

return new RikkaLayoutParams(mContext, attrs);

}

@Override

protected LayoutParams generateLayoutParams(LayoutParams p) {

return new RikkaLayoutParams§;

}

@Override

pAndroid 每周一个小*之 学习仿网易云广场歌单的效果
rotected LayoutParams generateDefaultLayoutParams() {

return new RikkaLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

}

4、关于测量


ViewGroup的宽度要么是写死的值,要么是三个子View之和

高度要么是写死的值,要么是三个View里面,最大的那一个:

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//先测量子View

measureChildren(widthMeasureSpec, heightMeasureSpec);

//因为这个时候已经测量完子View了,所以通过子View来计算整个View的宽高

int width = measureWidth(widthMeasureSpec);

int height = measureHeight(heightMeasureSpec);

//根据获取的宽高拿去用

setMeasuredDimension(width, height);

}

//整个View的宽度是三个子View的和

private int measureWidth(int widthMeasureSpec) {

int totalWidth = 0;

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

if (widthMode == MeasureSpec.EXACTLY) {

totalWidth = widthSize;

} else {

for (int i = 0; i < getChildCount(); i++) {

RikkaLayoutParams lp = (RikkaLayoutParams) getChildAt(i).getLayoutParams();

totalWidth += getChildAt(i).getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

}

}

return totalWidth;

}

//整个View的高 取三个View的最大值

private int measureHeight(int heightMeasureSpec) {

int maxHeight = 0;

int heightSize = MeasureSpec.getSize(heightMeasureSpec);

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

if (heightMode == MeasureSpec.EXACTLY) {

//如果是具体值就取具体值

maxHeight = heightSize;

} else {

for (int i = 0; i < getChildCount(); i++) {

View child = getChildAt(i);

RikkaLayoutParams lp = (RikkaLayoutParams) child.getLayoutParams();

maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);

}

}

return maxHeight;

}

5、关于布局


测量完后就需要布局了。

我们要根据三条辅助线来确定。最左边的View的辅助线,应该以左边为准,右边的以右边为准,中间的以中间为准。

由于在滑动时,View的位置也是要变的,也要不断的走onLayout方法,所以辅助线也是跟着变动的,它是跟着滑动百分比来计算的。

/**

  • 根据基准线去布局子View

  • 基准线有四条,子View分别在这四条线上

*/

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

for (int i = 0; i < getChildCount(); i++) {

int baseLineX = calBaseLine(i);

int baseLineY = getHeight() / 2;

//滑动的过程也是layout的过程,所以在layout的时候也要更改其透明度和缩放度

View child = getChildAt(i);

RikkaLayoutParams lp = (RikkaLayoutParams) child.getLayoutParams();

child.setScaleX(lp.getScale());

child.setScaleY(lp.getScale());

child.setAlpha(lp.getAlpha());

int left = baseLineX - child.getMeasuredWidth() / 2;

int top = baseLineY - child.getMeasuredHeight() / 2;

int right = left + child.getMeasuredWidth();

int bottom = top + child.getMeasuredHeight();

child.layout(left + lp.leftMargin + getPaddingLeft(),

top + lp.topMargin + getPaddingTop(),

right + lp.rightMargin + getPaddingRight(),

bottom + lp.bottomMargin + getPaddingBottom());

}

}

/**

  • 根据offsetPercent来计算基线位置,子View是根据基线来布局的

*/

private int calBaseLine(int index) {

float baseline = 0;

//最左边的baseline

float baselineLeft = getWidth() / 4;

//最中间的baseline

float baselineCenter = getWidth() / 2;

//最右边的baseline

float baselineRight = getWidth() - baselineLeft;

RikkaLayoutParams lp = (RikkaLayoutParams) getChildAt(index).getLayoutParams();

//根据lp的from 和 to来确定基线位置

switch (lp.getFrom()) {

case 0:

if (lp.getTo() == 1) {

baseline = baselineLeft + (baselineRight - baselineLeft) * -offsetPercent;

} else if (lp.getTo() == 2) {

baseline = baselineLeft + (baselineCenter - baselineLeft) * offsetPercent;

} else {

baseline = baselineLeft;

}

break;

case 1:

if (lp.getTo() == 0) {

baseline = baselineRight - (baselineRight - baselineLeft) * offsetPercent;

} else if (lp.getTo() == 2) {

baseline = baselineRight + (baselineRight - baselineCenter) * offsetPercent;

} else {

baseline = baselineRight;

}

break;

case 2:

if (lp.getTo() == 1) {

baseline = baselineCenter + (baselineRight - baselineCenter) * offsetPercent;

} else if (lp.getTo() == 0) {

baseline = baselineCenter + (baselineCenter - baselineLeft) * offsetPercent;

} else {

baseline = baselineCenter;

}

break;

}

return (int) baseline;

}

6、关于滑动、子View的移动


我们需要在onInterceptTouchEvent里判断一下我们是否需要使用到onTouchEvent,所以我们需要时时刻刻的去获取点击的位置,并记录偏移量,来判断是否是滑动状态,如果是的话,我们需要处理子View的移动了。

/**

  • 如果是滑动,则调用onTouchEvent,如果只是单击,可以切换View

*/

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

isDraged = false;

int x = (int) ev.getX();

int y = (int) ev.getY();

switch (ev.getActionMasked()) {

case MotionEvent.ACTION_DOWN:

mDownX = x;

mDownY = y;

mLastX = x;

mLastY = y;

break;

case MotionEvent.ACTION_MOVE:

//如果滑动超出规定的距离,则可以滑动View

int offsetX = (int) (x - mLastX);

int offsetY = (int) (y - mLastY);

if (Math.abs(offsetX) > MIN_SLOP_DISTANCE && Math.abs(offsetY) > MIN_SLOP_DISTANCE) {

mLastX = x;

mLastY = y;

isDraged = true;

}

case MotionEvent.ACTION_UP:

isDraged = false;

break;

}

return isDraged;

}

/**

  • onTouchEvent就是确定是要滑动了,根据滑动距离,做子View的位移动画

*/

@Override

public boolean onTouchEvent(MotionEvent event) {

int x = (int) event.getX();

int y = (int) event.getY();

switch (event.getActionMasked()) {

case MotionEvent.ACTION_DOWN:

case MotionEvent.ACTION_MOVE:

//通过总位移量除以View长来得到百分比

int offsetX = (int) (x - mLastX);

totalOffsetX += offsetX;

moveItem();

break;

case MotionEvent.ACTION_UP:

isDraged = false;

break;

}

mLastX = x;

mLastY = y;

//能走到onTouchEvent就肯定是返回true的

return true;

}

而子View就是根据 总位移量totalOffsetX来计算百分比的:

/**

  • 通过百分比的正负值来确定每个View要去到哪里、设置透明度和缩放、交换View的层级

*/

private void moveItem() {

offsetPercent = totalOffsetX / getWidth();

setViewFromAndTo();

changeViewLevel();

changeAlphaAndScale();

requestLayout();

}

/**

  • 根据百分比的正负值,来设置View的from和to

  • 如果是负则说明手指正在往左边滑动,则 0->1,1->2,2->0,反之亦然

*/

private void setViewFromAndTo() {

//如果滑动距离超出了屏幕的宽度,则超出的部分要更新

if (Math.abs(offsetPercent) >= 1) {

//在每次完整的滑完一次后,需要重置isReordered,不然当一次滑动很长距离时,会产生问题

isReordered = false;

for (int i = 0; i < getChildCount(); i++) {

RikkaLayoutParams lp = (RikkaLayoutParams) getChildAt(i).getLayoutParams();

lp.setFrom(lp.getTo());

}

totalOffsetX %= getWidth();

/**

  • 通过百分比的正负值来确定每个View要去到哪里、设置透明度和缩放、交换View的层级

*/

private void moveItem() {

offsetPercent = totalOffsetX / getWidth();

setViewFromAndTo();

changeViewLevel();

changeAlphaAndScale();

requestLayout();

}

/**

  • 根据百分比的正负值,来设置View的from和to

  • 如果是负则说明手指正在往左边滑动,则 0->1,1->2,2->0,反之亦然

*/

private void setViewFromAndTo() {

//如果滑动距离超出了屏幕的宽度,则超出的部分要更新

if (Math.abs(offsetPercent) >= 1) {

//在每次完整的滑完一次后,需要重置isReordered,不然当一次滑动很长距离时,会产生问题

isReordered = false;

for (int i = 0; i < getChildCount(); i++) {

RikkaLayoutParams lp = (RikkaLayoutParams) getChildAt(i).getLayoutParams();

lp.setFrom(lp.getTo());

}

totalOffsetX %= getWidth();

上一篇:USTC English Club Note20170919


下一篇:序列模型的一些小总结