
实现图片的缩放,平移,双击缩放等基本功能的代码如下,每一行代码我都做了详细的注释
public class ZoomImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener,View.OnTouchListener , ViewTreeObserver.OnGlobalLayoutListener{ /*** 缩放手势的监测*/ private ScaleGestureDetector mScaleGestureDetector; /*** 监听手势*/ private GestureDetector mGestureDetector; /*** 对图片进行缩放平移的Matrix*/ private Matrix mScaleMatrix; /*** 第一次加载图片时调整图片缩放比例,使图片的宽或者高充满屏幕*/ private boolean mFirst; /*** 图片的初始化比例*/ private float mInitScale; /*** 图片的最大比例*/ private float mMaxScale; /*** 双击图片放大的比例*/ private float mMidScale; /*** 是否正在自动放大或者缩小*/ private boolean isAutoScale; //----------------------------------------------- /*** 上一次触控点的数量*/ private int mLastPointerCount; /*** 是否可以拖动*/ private boolean isCanDrag; /*** 上一次滑动的x和y坐标*/ private float mLastX; private float mLastY; /*** 可滑动的临界值*/ private int mTouchSlop; /*** 是否用检查左右边界*/ private boolean isCheckLeftAndRight; /*** 是否用检查上下边界*/ private boolean isCheckTopAndBottom; public ZoomImageView(Context context) {this(context, null, 0); } public ZoomImageView(Context context, AttributeSet attrs) {this(context, attrs, 0); } public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//一定要将图片的ScaleType设置成Matrix类型的setScaleType(ScaleType.MATRIX);//初始化缩放手势监听器mScaleGestureDetector = new ScaleGestureDetector(context,this);//初始化矩阵mScaleMatrix = new Matrix();setOnTouchListener(this);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();//初始化手势检测器,监听双击事件mGestureDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onDoubleTap(MotionEvent e) {//如果是正在自动缩放,则直接返回,不进行处理if (isAutoScale) return true;//得到点击的坐标float x = e.getX();float y = e.getY();//如果当前图片的缩放值小于指定的双击缩放值if (getScale() < mMidScale){ //进行自动放大 post(new AutoScaleRunnable(mMidScale,x,y));}else{ //当前图片的缩放值大于初试缩放值,则自动缩小 post(new AutoScaleRunnable(mInitScale,x,y));}return true; }}); } /*** 当view添加到window时调用,早于onGlobalLayout,因此可以在这里注册监听器*/ @Override protected void onAttachedToWindow() {super.onAttachedToWindow();getViewTreeObserver().addOnGlobalLayoutListener(this); } /*** 当view从window上移除时调用,因此可以在这里移除监听器*/ @Override protected void onDetachedFromWindow() {super.onDetachedFromWindow();getViewTreeObserver().removeGlobalOnLayoutListener(this); } /*** 当布局树发生变化时会调用此方法,我们可以在此方法中获得控件的宽和高*/ @Override public void onGlobalLayout() {//只有当第一次加载图片的时候才会进行初始化,用一个变量mFirst控制if (!mFirst){ mFirst = true; //得到控件的宽和高 int width = getWidth(); int height = getHeight(); //得到当前ImageView中加载的图片 Drawable d = getDrawable(); if(d == null){//如果没有图片,则直接返回return; } //得到当前图片的宽和高,图片的宽和高不一定等于控件的宽和高 //因此我们需要将图片的宽和高与控件宽和高进行判断 //将图片完整的显示在屏幕中 int dw = d.getIntrinsicWidth(); int dh = d.getIntrinsicHeight(); //我们定义一个临时变量,根据图片与控件的宽高比例,来确定这个最终缩放值 float scale = 1.0f; //如果图片宽度大于控件宽度,图片高度小于控件高度 if (dw>width && dh<height){//我们需要将图片宽度缩小,缩小至控件的宽度//至于为什么要这样计算,我们可以这样想//我们调用matrix.postScale(scale,scale)时,宽和高都要乘以scale的//当前我们的图片宽度是dw,dw*scale=dw*(width/dw)=width,这样就等于控件宽度了//我们的高度同时也乘以scale,这样能够保证图片的宽高比不改变,图片不变形scale = width * 1.0f / dw; } //如果图片的宽度小于控件宽度,图片高度大于控件高度 if (dw<width && dh>height){//我们就应该将图片的高度缩小,缩小至控件的高度,计算方法同上scale = height * 1.0f / dh; } //如果图片的宽度小于控件宽度,高度小于控件高度时,我们应该将图片放大 //比如图片宽度是控件宽度的1/2 ,图片高度是控件高度的1/4 //如果我们将图片放大4倍,则图片的高度是和控件高度一样了,但是图片宽度就超出控件宽度了 //因此我们应该选择一个最小值,那就是将图片放大2倍,此时图片宽度等于控件宽度 //同理,如果图片宽度大于控件宽度,图片高度大于控件高度,我们应该将图片缩小 //缩小的倍数也应该为那个最小值 if ((dw < width && dh < height) || (dw > width && dh > height)){scale = Math.min(width * 1.0f / dw , height * 1.0f / dh); } //我们还应该对图片进行平移操作,将图片移动到屏幕的居中位置 //控件宽度的一半减去图片宽度的一半即为图片需要水平移动的距离 //高度同理,大家可以画个图看一看 int dx = width/2 - dw/2; int dy = height/2 - dh/2; //对图片进行平移,dx和dy分别表示水平和竖直移动的距离 mScaleMatrix.postTranslate(dx, dy); //对图片进行缩放,scale为缩放的比例,后两个参数为缩放的中心点 mScaleMatrix.postScale(scale, scale, width / 2, height / 2); //将矩阵作用于我们的图片上,图片真正得到了平移和缩放 setImageMatrix(mScaleMatrix); //初始化一下我们的几个缩放的边界值 mInitScale = scale; //最大比例为初始比例的4倍 mMaxScale = mInitScale * 4; //双击放大比例为初始化比例的2倍 mMidScale = mInitScale * 2;} } /*** 获得图片当前的缩放比例值*/ private float getScale(){//Matrix为一个3*3的矩阵,一共9个值float[] values = new float[9];//将Matrix的9个值映射到values数组中mScaleMatrix.getValues(values);//拿到Matrix中的MSCALE_X的值,这个值为图片宽度的缩放比例,因为图片高度//的缩放比例和宽度的缩放比例一致,我们取一个就可以了//我们还可以 return values[Matrix.MSCALE_Y];return values[Matrix.MSCALE_X]; } /*** 获得缩放后图片的上下左右坐标以及宽高*/ private RectF getMatrixRectF(){//获得当钱图片的矩阵Matrix matrix = mScaleMatrix;//创建一个浮点类型的矩形RectF rectF = new RectF();//得到当前的图片Drawable d = getDrawable();if (d != null){ //使这个矩形的宽和高同当前图片一致 rectF.set(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight()); //将矩阵映射到矩形上面,之后我们可以通过获取到矩阵的上下左右坐标以及宽高 //来得到缩放后图片的上下左右坐标和宽高 matrix.mapRect(rectF);}return rectF; } /*** 当缩放时检查边界并且使图片居中*/ private void checkBorderAndCenterWhenScale(){if (getDrawable() == null){ return;}//初始化水平和竖直方向的偏移量float deltaX = 0.0f;float deltaY = 0.0f;//得到控件的宽和高int width = getWidth();int height = getHeight();//拿到当前图片对应的矩阵RectF rectF = getMatrixRectF();//如果当前图片的宽度大于控件宽度,当前图片处于放大状态if (rectF.width() >= width){ //如果图片左边坐标是大于0的,说明图片左边离控件左边有一定距离, //左边会出现一个小白边 if (rectF.left > 0){//我们将图片向左边移动deltaX = -rectF.left; } //如果图片右边坐标小于控件宽度,说明图片右边离控件右边有一定距离, //右边会出现一个小白边 if (rectF.right <width){//我们将图片向右边移动deltaX = width - rectF.right; }}//上面是调整宽度,这是调整高度if (rectF.height() >= height){ //如果上面出现小白边,则向上移动 if (rectF.top > 0){deltaY = -rectF.top; } //如果下面出现小白边,则向下移动 if (rectF.bottom < height){deltaY = height - rectF.bottom; }}//如果图片的宽度小于控件的宽度,我们要对图片做一个水平的居中if (rectF.width() < width){ deltaX = width/2f - rectF.right + rectF.width()/2f;}//如果图片的高度小于控件的高度,我们要对图片做一个竖直方向的居中if (rectF.height() < height){ deltaY = height/2f - rectF.bottom + rectF.height()/2f;}//将平移的偏移量作用到矩阵上mScaleMatrix.postTranslate(deltaX, deltaY); } /*** 平移时检查上下左右边界*/ private void checkBorderWhenTranslate() {//获得缩放后图片的相应矩形RectF rectF = getMatrixRectF();//初始化水平和竖直方向的偏移量float deltaX = 0.0f;float deltaY = 0.0f;//得到控件的宽度int width = getWidth();//得到控件的高度int height = getHeight();//如果是需要检查左和右边界if (isCheckLeftAndRight){ //如果左边出现的白边 if (rectF.left > 0){//向左偏移deltaX = -rectF.left; } //如果右边出现的白边 if (rectF.right < width){//向右偏移deltaX = width - rectF.right; }}//如果是需要检查上和下边界if (isCheckTopAndBottom){ //如果上面出现白边 if (rectF.top > 0){//向上偏移deltaY = -rectF.top; } //如果下面出现白边 if (rectF.bottom < height){//向下偏移deltaY = height - rectF.bottom; }}mScaleMatrix.postTranslate(deltaX,deltaY); } /*** 自动放大缩小,自动缩放的原理是使用View.postDelay()方法,每隔16ms调用一次* run方法,给人视觉上形成一种动画的效果*/ private class AutoScaleRunnable implements Runnable{//放大或者缩小的目标比例private float mTargetScale;//可能是BIGGER,也可能是SMALLERprivate float tempScale;//放大缩小的中心点private float x;private float y;//比1稍微大一点,用于放大private final float BIGGER = 1.07f;//比1稍微小一点,用于缩小private final float SMALLER = 0.93f;//构造方法,将目标比例,缩放中心点传入,并且判断是要放大还是缩小public AutoScaleRunnable(float targetScale , float x , float y){ this.mTargetScale = targetScale; this.x = x; this.y = y; //如果当前缩放比例小于目标比例,说明要自动放大 if (getScale() < mTargetScale){//设置为BiggertempScale = BIGGER; } //如果当前缩放比例大于目标比例,说明要自动缩小 if (getScale() > mTargetScale){//设置为SmallertempScale = SMALLER; }}@Overridepublic void run() { //这里缩放的比例非常小,只是稍微比1大一点或者比1小一点的倍数 //但是当每16ms都放大或者缩小一点点的时候,动画效果就出来了 mScaleMatrix.postScale(tempScale, tempScale, x, y); //每次将矩阵作用到图片之前,都检查一下边界 checkBorderAndCenterWhenScale(); //将矩阵作用到图片上 setImageMatrix(mScaleMatrix); //得到当前图片的缩放值 float currentScale = getScale(); //如果当前想要放大,并且当前缩放值小于目标缩放值 //或者 当前想要缩小,并且当前缩放值大于目标缩放值 if ((tempScale > 1.0f) && currentScale < mTargetScale ||(tempScale < 1.0f) && currentScale > mTargetScale){//每隔16ms就调用一次run方法postDelayed(this,16); }else {//current*scale=current*(mTargetScale/currentScale)=mTargetScale//保证图片最终的缩放值和目标缩放值一致float scale = mTargetScale / currentScale;mScaleMatrix.postScale(scale, scale, x, y);checkBorderAndCenterWhenScale();setImageMatrix(mScaleMatrix);//自动缩放结束,置为falseisAutoScale = false; }} } /*** 这个是OnScaleGestureListener中的方法,在这个方法中我们可以对图片进行放大缩小*/ @Override public boolean onScale(ScaleGestureDetector detector) {//当我们两个手指进行分开操作时,说明我们想要放大,这个scaleFactor是一个稍微大于1的数值//当我们两个手指进行闭合操作时,说明我们想要缩小,这个scaleFactor是一个稍微小于1的数值float scaleFactor = detector.getScaleFactor();//获得我们图片当前的缩放值float scale = getScale();//如果当前没有图片,则直接返回if (getDrawable() == null){ return true;}//如果scaleFactor大于1,说明想放大,当前的缩放比例乘以scaleFactor之后小于//最大的缩放比例时,允许放大//如果scaleFactor小于1,说明想缩小,当前的缩放比例乘以scaleFactor之后大于//最小的缩放比例时,允许缩小if ((scaleFactor > 1.0f && scale * scaleFactor < mMaxScale)|| scaleFactor < 1.0f && scale * scaleFactor > mInitScale){ //边界控制,如果当前缩放比例乘以scaleFactor之后大于了最大的缩放比例 if (scale * scaleFactor > mMaxScale + 0.01f){//则将scaleFactor设置成mMaxScale/scale//当再进行matrix.postScale时//scale*scaleFactor=scale*(mMaxScale/scale)=mMaxScale//最后图片就会放大至mMaxScale缩放比例的大小scaleFactor = mMaxScale / scale; } //边界控制,如果当前缩放比例乘以scaleFactor之后小于了最小的缩放比例 //我们不允许再缩小 if (scale * scaleFactor < mInitScale + 0.01f){//计算方法同上scaleFactor = mInitScale / scale; } //前两个参数是缩放的比例,是一个稍微大于1或者稍微小于1的数,形成一个随着手指放大 //或者缩小的效果 //detector.getFocusX()和detector.getFocusY()得到的是多点触控的中点 //这样就能实现我们在图片的某一处局部放大的效果 mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); //因为图片的缩放点不是图片的中心点了,所以图片会出现偏移的现象,所以进行一次边界的检查和居中操作 checkBorderAndCenterWhenScale(); //将矩阵作用到图片上 setImageMatrix(mScaleMatrix);}return true; } /*** 一定要返回true*/ @Override public boolean onScaleBegin(ScaleGestureDetector detector) {return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { } @Override public boolean onTouch(View v, MotionEvent event) {//当双击操作时,不允许移动图片,直接返回trueif (mGestureDetector.onTouchEvent(event)){ return true;}//将事件传递给ScaleGestureDetectormScaleGestureDetector.onTouchEvent(event);//用于存储多点触控产生的坐标float x = 0.0f;float y = 0.0f;//得到多点触控的个数int pointerCount = event.getPointerCount();//将所有触控点的坐标累加起来for(int i=0 ; i<pointerCount ; i++){ x += event.getX(i); y += event.getY(i);}//取平均值,得到的就是多点触控后产生的那个点的坐标x /= pointerCount;y /= pointerCount;//如果触控点的数量变了,则置为不可滑动if (mLastPointerCount != pointerCount){ isCanDrag = false; mLastX = x; mLastY = y;}mLastPointerCount = pointerCount;RectF rectF = getMatrixRectF();switch (event.getAction()){ case MotionEvent.ACTION_DOWN:isCanDrag = false;//当图片处于放大状态时,禁止ViewPager拦截事件,将事件传递给图片,进行拖动if (rectF.width() > getWidth() + 0.01f || rectF.height() > getHeight() + 0.01f){ if (getParent() instanceof ViewPager){getParent().requestDisallowInterceptTouchEvent(true); }}break; case MotionEvent.ACTION_MOVE://当图片处于放大状态时,禁止ViewPager拦截事件,将事件传递给图片,进行拖动if (rectF.width() > getWidth() + 0.01f || rectF.height() > getHeight() + 0.01f){ if (getParent() instanceof ViewPager){getParent().requestDisallowInterceptTouchEvent(true); }}//得到水平和竖直方向的偏移量float dx = x - mLastX;float dy = y - mLastY;//如果当前是不可滑动的状态,判断一下是否是滑动的操作if (!isCanDrag){ isCanDrag = isMoveAction(dx,dy);}//如果可滑动if (isCanDrag){ if (getDrawable() != null){isCheckLeftAndRight = true;isCheckTopAndBottom = true;//如果图片宽度小于控件宽度if (rectF.width() < getWidth()){ //左右不可滑动 dx = 0; //左右不可滑动,也就不用检查左右的边界了 isCheckLeftAndRight = false;}//如果图片的高度小于控件的高度if (rectF.height() < getHeight()){ //上下不可滑动 dy = 0; //上下不可滑动,也就不用检查上下边界了 isCheckTopAndBottom = false;} } mScaleMatrix.postTranslate(dx,dy); //当平移时,检查上下左右边界 checkBorderWhenTranslate(); setImageMatrix(mScaleMatrix);}mLastX = x;mLastY = y;break; case MotionEvent.ACTION_UP://当手指抬起时,将mLastPointerCount置0,停止滑动mLastPointerCount = 0;break; case MotionEvent.ACTION_CANCEL:break;}return true; } /*** 判断是否是移动的操作*/ private boolean isMoveAction(float dx , float dy){//勾股定理,判断斜边是否大于可滑动的一个临界值return Math.sqrt(dx*dx + dy*dy) > mTouchSlop; }}实现图片缩小后,松手回弹的效果 /*** 最小缩放比例*/ private float mMinScale;//在onGlobalLayout中进行初始化 @Override public void onGlobalLayout() { ... //最小缩放比例为初试比例的1/4倍 mMinScale = mInitScale / 4; ... }//在onScale中,修改如下代码 @Override public boolean onScale(ScaleGestureDetector detector) { ...if ((scaleFactor > 1.0f && scale * scaleFactor < mMaxScale)|| scaleFactor < 1.0f && scale * scaleFactor > mMinScale){ //边界控制,如果当前缩放比例乘以scaleFactor之后小于了最小的缩放比例 //我们不允许再缩小 if (scale * scaleFactor < mMinScale + 0.01f){scaleFactor = mMinScale / scale; } ... }这样我们的图片最小就可以缩放到初始化比例的1/4大小了,然后我们还需要添加一个松手后回弹至初试化大小的动画效果,然后我们需要在onTouch的ACTION_UP中添加如下代码 @Override public boolean onTouch(View v, MotionEvent event) { ...case MotionEvent.ACTION_UP://当手指抬起时,将mLastPointerCount置0,停止滑动mLastPointerCount = 0;//如果当前图片大小小于初始化大小if (getScale() < mInitScale){ //自动放大至初始化大小 post(new AutoScaleRunnable(mInitScale,getWidth()/2,getHeight()/2));}break; ... }现在我们看一下效果 
实现图片放大后,松手回弹效果
这个功能实现起来和上面那个功能基本一致,大家可以先试着自己写一下。
同理,我们需要先定义一个mMaxOverScale作为放大到最大值后,还能继续放大到的值。
/*** 最大溢出值*/ private float mMaxOverScale;//在onGlobalLayout中进行初始化 @Override public void onGlobalLayout() { ... //最大溢出值为最大值的5倍,可以随意调 mMaxOverScale = mMaxScale * 5; ... }//在onScale中,修改如下代码 @Override public boolean onScale(ScaleGestureDetector detector) { ...if ((scaleFactor > 1.0f && scale * scaleFactor < mMaxOverScale)|| scaleFactor < 1.0f && scale * scaleFactor > mMinScale){ if (scale * scaleFactor > mMaxOverScale + 0.01f){scaleFactor = mMaxOverScale / scale; } ... }这样当我们图片放大至最大比例后还可以继续放大,然后我们同样需要在onTouch中的ACTION_UP中添加自动缩小的功能 case MotionEvent.ACTION_UP://当手指抬起时,将mLastPointerCount置0,停止滑动mLastPointerCount = 0;//如果当前图片大小小于初始化大小if (getScale() < mInitScale){ //自动放大至初始化大小 post(new AutoScaleRunnable(mInitScale,getWidth()/2,getHeight()/2));}//如果当前图片大小大于最大值if (getScale() > mMaxScale){ //自动缩小至最大值 post(new AutoScaleRunnable(mMaxScale,getWidth()/2,getHeight()/2));}break;然后我们看一下效果 
实现图片的惯性滑动
要实现图片的惯性滑动,我们需要借助VelocityTracker来帮我们检测当我们手指离开图片时的一个速度,然后根据这个速度以及图片的位置来调用Scroller的fling方法来计算惯性滑动过程中的x和y的坐标
@Override public boolean onTouch(View v, MotionEvent event) { ...switch (event.getAction()){ case MotionEvent.ACTION_DOWN://初始化速度检测器mVelocityTracker = VelocityTracker.obtain();if (mVelocityTracker != null){ //将当前的事件添加到检测器中 mVelocityTracker.addMovement(event);}//当手指再次点击到图片时,停止图片的惯性滑动if (mFlingRunnable != null){ mFlingRunnable.cancelFling(); mFlingRunnable = null;}...}...case MotionEvent.ACTION_MOVE:...//如果可滑动if (isCanDrag){ if (getDrawable() != null){if (mVelocityTracker != null){ //将当前事件添加到检测器中 mVelocityTracker.addMovement(event);}...}...case MotionEvent.ACTION_UP://当手指抬起时,将mLastPointerCount置0,停止滑动mLastPointerCount = 0;//如果当前图片大小小于初始化大小if (getScale() < mInitScale){ //自动放大至初始化大小 post(new AutoScaleRunnable(mInitScale,getWidth()/2,getHeight()/2));}//如果当前图片大小大于最大值if (getScale() > mMaxScale){ //自动缩小至最大值 post(new AutoScaleRunnable(mMaxScale,getWidth()/2,getHeight()/2));}if (isCanDrag){//如果当前可以滑动 if (mVelocityTracker != null){//将当前事件添加到检测器中mVelocityTracker.addMovement(event);//计算当前的速度mVelocityTracker.computeCurrentVelocity(1000);//得到当前x方向速度final float vX = mVelocityTracker.getXVelocity();//得到当前y方向的速度final float vY = mVelocityTracker.getYVelocity();mFlingRunnable = new FlingRunnable(getContext());//调用fling方法,传入控件宽高和当前x和y轴方向的速度//这里得到的vX和vY和scroller需要的velocityX和velocityY的负号正好相反//所以传入一个负值mFlingRunnable.fling(getWidth(),getHeight(),(int)-vX,(int)-vY);//执行run方法post(mFlingRunnable); }}break; case MotionEvent.ACTION_CANCEL://释放速度检测器if (mVelocityTracker != null){ mVelocityTracker.recycle(); mVelocityTracker = null;}break;/*** 惯性滑动*/ private class FlingRunnable implements Runnable{private Scroller mScroller;private int mCurrentX , mCurrentY;public FlingRunnable(Context context){ mScroller = new Scroller(context);}public void cancelFling(){ mScroller.forceFinished(true);}/** * 这个方法主要是从onTouch中或得到当前滑动的水平和竖直方向的速度 * 调用scroller.fling方法,这个方法内部能够自动计算惯性滑动 * 的x和y的变化率,根据这个变化率我们就可以对图片进行平移了 */public void fling(int viewWidth , int viewHeight , int velocityX , int velocityY){ RectF rectF = getMatrixRectF(); if (rectF == null){return; } //startX为当前图片左边界的x坐标 final int startX = Math.round(-rectF.left); final int minX , maxX , minY , maxY; //如果图片宽度大于控件宽度 if (rectF.width() > viewWidth){//这是一个滑动范围[minX,maxX],详情见下图minX = 0;maxX = Math.round(rectF.width() - viewWidth); }else{//如果图片宽度小于控件宽度,则不允许滑动minX = maxX = startX; } //如果图片高度大于控件高度,同理 final int startY = Math.round(-rectF.top); if (rectF.height() > viewHeight){minY = 0;maxY = Math.round(rectF.height() - viewHeight); }else{minY = maxY = startY; } mCurrentX = startX; mCurrentY = startY; if (startX != maxX || startY != maxY){//调用fling方法,然后我们可以通过调用getCurX和getCurY来获得当前的x和y坐标//这个坐标的计算是模拟一个惯性滑动来计算出来的,我们根据这个x和y的变化可以模拟//出图片的惯性滑动mScroller.fling(startX,startY,velocityX,velocityY,minX,maxX,minY,maxY); }}关于startX,minX,maxX做一个解释 
我们从图中可以看出,当前图片可滑动的一个区间就是左边多出来的那块区间,所以minX和maxX代表的是区间的最小值和最大值,startX就是屏幕左边界的坐标值,我们可以想象成是startX在区间[minX,maxX]的移动。Y轴方向同理。
现在我们看一下效果

以上就是本文的全部内容,希望对大家学习Android软件编程有所帮助。