您的位置:首页 > 移动开发 > Android开发

利用com.graphics.Camera 模拟ViewPager布局3D效果

2016-11-02 13:43 393 查看
学习了很多自定义View的知识,终于有勇气自己写一个Demo的勇气了,还是要多实践啊!!!!!!!!!

记得在AndroidManifest.xml中关闭硬件加速!!!!!!!

android:hardwareAccelerated="false"


需要掌握的内容:坐标系等基础知识,View的绘制过程,画布的操作,Matrix原理,Matrix Camera原理,事件分发机制等。

这里我推荐一个网站,里面的内容很丰富也很有趣

http://www.gcssloop.com/customview/CustomViewIndex

下面介绍如何开始下笔!!!

首先我们要创建一个类,并继承ViewGroup,规定一些变量,用途都在注释里写了,还有构造函数

public enum State {
ToPre, ToNext
}

private State state;//标记滚动的状态
private int mWidth, mHeight;//ViewGroup的宽高
private float mDownX, mMoveX = 0;//按下的坐标和移动时的坐标
private VelocityTracker mVelocityTracker;//测速
private float mAngle = 90;//两个item间的夹角

private int standerSpeed = 200;//规定的手指移动速率

private int a = -2500;//阻力

private Scroller mScroller;
private Camera mCamera;
private Matrix mMatrix;

private boolean flag = false;//标记是否在增加view

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

public MyView(Context context, AttributeSet attributeSet){
super(context, attributeSet);
//这是一个滚动计算器,可以计算出滚动一段距离的中间过程
mScroller = new Scroller(context);
mCamera = new Camera();
mMatrix = new Matrix();
}


然后就是onLayout onMeasure方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
//因为一个ViewGroup里有很多个子布局,这些都在布局文件里写好的,不懂的可以看看源码
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() == VISIBLE) {
child.layout(childLeft,
0,
childLeft + child.getMeasuredWidth(),
child.getMeasuredHeight());
childLeft = childLeft + child.getMeasuredWidth();
}
}
}


下面开始重头戏,根据手指移动的距离,使ViewGroup滚动起来

@Override
public boolean onTouchEvent(MotionEvent event) {
//创建一个速度计算器,可以计算出手指移动时的速率
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
//将这个事件添加到速度计算器中
mVelocityTracker.addMovement(event);

//当事件是移动时
if (event.getAction() == MotionEvent.ACTION_MOVE){
//拿到当前的坐标x
float x = event.getX();
//用之前记录的值减去x,得到移动的距离
int tempX = (int)(mDownX - x);
//将当前的x记录下来
mDownX = x;
//根据移动的距离tempX滚动ViewGroup
scrollBy(tempX, 0);
//根据距离判断首尾view的添加,保证循环滚动
changeView();
}else if(event.getAction() == MotionEvent.ACTION_DOWN){
//如果事件是按下,记录当前按下的坐标x
mDownX = event.getX();
}else if(event.getAction() == MotionEvent.ACTION_UP){
//通过一个速率的单位计算速率的值
mVelocityTracker.computeCurrentVelocity(1000);
//往左滑小于0   往右滑大于0
float xVelocity = mVelocityTracker.getXVelocity();
//滑动的速度大于规定的速度时,开始自动减速滑动直至静止
if (xVelocity > standerSpeed) {
//往前翻滚
state = State.ToPre;
//根据重力加速度计算需要滑动的路程
float s = xVelocity * (-xVelocity / a) / 2;
//计算需要滑动的时间
int t = (int) (-xVelocity / a * 2000);
//开始计算滑动过程
mScroller.startScroll(0, 0, (int) s, 0, t);
}else if(xVelocity < -standerSpeed){
//往后翻滚
state = State.ToNext;
//根据重力加速度计算需要滑动的路程
//消掉速度的单位
xVelocity = -xVelocity;
float s = xVelocity * (-xVelocity / a) / 2;
//计算需要滑动的时间
int t = (int) (-xVelocity / a * 2000);
//开始计算滑动过程
mScroller.startScroll(0, 0, (int) s, 0, t);
}
}
return true;
}

/**
* 这个方法主要是用来在手指抬起后,计算ViewGroup还要再继续滚动多远
*/
@Override
public void computeScroll() {
//当滚动计算器还在计算时,并且现在没有在改变子View的位置
if (mScroller.computeScrollOffset() && flag == false) {
if (state == State.ToPre) {
//拿到滚动计算器计算出的值,并与上一次的值相减,得出应该滚动的距离
int tempX = (int) (mScroller.getCurrX() - mMoveX);
//将当前的值记录下来
mMoveX = mScroller.getCurrX();
//使ViewGroup滚动-tempX的距离,注意正负方向
scrollBy(-tempX, 0);
}else if(state == State.ToNext){
//拿到滚动计算器计算出的值,并与上一次的值相减,得出应该滚动的距离
int tempX = (int) (mScroller.getCurrX() - mMoveX);
//将当前的值记录下来
mMoveX = mScroller.getCurrX();
//使ViewGroup滚动tempX的距离,注意正负方向
scrollBy(tempX, 0);
}
//刷新界面
invalidate();
}
//当这一次自动计算结束时,给mMoveX清零
if(mScroller.isFinished())
mMoveX = 0;
//判断一下是否需要在首尾添加界面
changeView();
}

/**
* 当超过规定位置时,增加首尾的页数,达到拼接的效果
*/
private void changeView(){
if (getScrollX() < 20){
addPre();
LogUtil.d("TAG", "前面加了一页");
}else if(getScrollX() > (getChildCount() - 1) * mWidth - 20){
addNext();
LogUtil.d("TAG", "后面加了一页");
}
}

/**
* 把第一个item移动到最后一个item位置
*/
private void addNext() {
int childCount = getChildCount();
View view = getChildAt(0);
removeViewAt(0);
addView(view, childCount - 1);
scrollBy(-mWidth, 0);
}

/**
* 把最后一个item移动到第一个item位置
*/
private void addPre() {
int childCount = getChildCount();
View view = getChildAt(childCount - 1);
flag = true;
removeViewAt(childCount - 1);
addView(view, 0);
scrollBy(mWidth, 0);
flag = false;
}


这时已经实现了循环的左右平移,想要实现3D的效果还需要下面的步骤

/**
* 在ViewGroup中需要重写这个方法
*/
@Override
protected void dispatchDraw(Canvas canvas) {
//遍历子View,并画出来
for (int i = 0; i < getChildCount(); i++) {
//画3D的方法
drawScreen(canvas, i, getDrawingTime());
}
//如果不想要3D效果只想实现平移,注释掉上面的代码,开启下面的代码即可
//        super.dispatchDraw(canvas);
}

/**
* 画3D效果
*/
private void drawScreen(Canvas canvas, int i, long drawingTime){
int curScreenX = mWidth * i;
//屏幕中不显示的部分不进行绘制
if (getScrollX() + mWidth < curScreenX) {
return;
}
if (curScreenX < getScrollX() - mWidth) {
return;
}

//计算中心坐标
float centerX = (getScrollX() > curScreenX) ? curScreenX + mWidth : curScreenX;
float centerY = mHeight / 2;
//计算角度
float degree = mAngle * (getScrollX() - curScreenX) / mWidth;
if (degree > 90 || degree < -90) {
return;
}
canvas.save();

//利用Camera进行一次旋转拍照
mCamera.save();
mCamera.rotateY(-degree);
//并将结果保存在Matrix矩阵中
mCamera.getMatrix(mMatrix);
mCamera.restore();

//将矩阵移动到视图中心位置
mMatrix.preTranslate(-centerX, -centerY);
mMatrix.postTranslate(centerX, centerY);
//将矩阵设置到画布上
canvas.concat(mMatrix);
//画子View
drawChild(canvas, getChildAt(i), drawingTime);
canvas.restore();
}


现在已经可以实现3D的效果了,实现的过程我写了很清楚的注释。主要实现3D的效果还是靠Camera和Matrix类,具体的原理还是需要自己去研究了。

源码:https://github.com/Techck/3DFlipViewPager
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  自定义View android