Android 自定义view 折线翻页原理笔记

看了Aige的  Android翻页效果原理实现之引入折线
有些计算原理 在此留个笔记


x、y 为 折出的三角形的 短边与长边; O(a,b)点即为触摸点
设K = w - a, L = h - b

∆OMA中,由勾股定理,得出


∆OMA与 ∆AOB、∆APB三者之面积和 等于 梯形 MOBP的面积

代入x,解得

再代入触摸点(a,b) 即可求出当前对应的x、y了

有x、y现在就可以求出A点和B点的坐标了
A点(w - x, h)
B点(w, h - y)
折线出的三角形即是:以Path的moveTo到touch点,再lineTo到A、B两点,close闭合出的三角形


AB为对折线,P点对折形成的O点应该在 以(0,h)为圆心,w为半径的圆内
由Path和Region求出这个圆的范围: mRegionShortSize
如果不在范围内,那肯定是触摸的纵坐标出了问题:
[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. if (!mRegionShortSize.contains((int)mTouchX, (int)mTouchY)) {  
  2.     /* 
  3.      如果不在则通过x坐标强行重算y坐标 
  4.      通过圆的标准方程: (x-a)^2+(y-b)^2=r^2 (a,b)为圆心 r为半径  x,y为圆弧上的一点 
  5.      y - b = Math.sqrt(r^2 - (x-a)^2)  => y = Math.sqrt(r^2 - (x-a)^2) + b 
  6.      或 
  7.      -(y - b) = Math.sqrt(r^2 - (x-a)^2) => y = -1 * Math.sqrt(r^2 - (x-a)^2) + b 
  8.       */  
  9. //  mTouchY = (float) (Math.sqrt((Math.pow(mW, 2) - Math.pow(mTouchX, 2))) + mH); // 明显值大于mH,不对  
  10.     mTouchY = (float) (-1 * Math.sqrt((Math.pow(mW, 2) - Math.pow(mTouchX, 2))) + mH);  
  11.   
  12. }  
这样算出来的mTouchY即是圆的轨迹上的对应横坐标的纵坐标值
(注:原文中的表达式
mPointY = (float) (Math.sqrt((Math.pow(mViewWidth, 2) - Math.pow(mPointX, 2))) - mViewHeight);  
mPointY = Math.abs(mPointY) + mValueAdded; 
 )

到目前的代码:
[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package com.stone.turnpage.view;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.graphics.Color;  
  6. import android.graphics.Paint;  
  7. import android.graphics.Path;  
  8. import android.graphics.RectF;  
  9. import android.graphics.Region;  
  10. import android.os.Build;  
  11. import android.support.annotation.RequiresApi;  
  12. import android.util.AttributeSet;  
  13. import android.view.MotionEvent;  
  14. import android.view.View;  
  15.   
  16. /** 
  17.  * author : stone 
  18.  * email  : aa86799@163.com 
  19.  * time   : 16/9/20 11 18 
  20.  * 
  21.  * 折线翻页 
  22.  */  
  23.   
  24. public class FoldTurnPageView extends View {  
  25.   
  26.     private float mTouchX, mTouchY;  
  27.     private Path mPath;  
  28.     private Paint mPaint;  
  29.     private int mW, mH;  
  30.     private Region mRegionShortSize;  
  31.   
  32.     public FoldTurnPageView(Context context) {  
  33.         this(context, null);  
  34.     }  
  35.   
  36.     public FoldTurnPageView(Context context, AttributeSet attrs) {  
  37.         this(context, attrs, 0);  
  38.     }  
  39.   
  40.     public FoldTurnPageView(Context context, AttributeSet attrs, int defStyleAttr) {  
  41.         super(context, attrs, defStyleAttr);  
  42.   
  43.         mPath = new Path();  
  44.         mPaint = new Paint();  
  45.         mPaint.setColor(Color.RED);  
  46.         mPaint.setStyle(Paint.Style.STROKE);  
  47.         mPaint.setStrokeWidth(10);  
  48.   
  49.         mRegionShortSize = new Region();  
  50.     }  
  51.   
  52.     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)  
  53.     public FoldTurnPageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
  54.         super(context, attrs, defStyleAttr, defStyleRes);  
  55.     }  
  56.   
  57.     @Override  
  58.     public boolean onTouchEvent(MotionEvent event) {  
  59.         mTouchX = event.getX();  
  60.         mTouchY = event.getY();  
  61.         System.out.println(mTouchY);  
  62.         if (!mRegionShortSize.contains((int)mTouchX, (int)mTouchY)) {  
  63.             /* 
  64.              如果不在则通过x坐标强行重算y坐标 
  65.              通过圆的标准方程: (x-a)^2+(y-b)^2=r^2 (a,b)为圆心 r为半径  x,y为圆弧上的一点 
  66.              y - b = Math.sqrt(r^2 - (x-a)^2)  => y = Math.sqrt(r^2 - (x-a)^2) + b 
  67.              或 
  68.              -(y - b) = Math.sqrt(r^2 - (x-a)^2) => y = -1 * Math.sqrt(r^2 - (x-a)^2) + b 
  69.               */  
  70. //            mTouchY = (float) (Math.sqrt((Math.pow(mW, 2) - Math.pow(mTouchX, 2))) + mH);  
  71.             mTouchY = (float) (-1 * Math.sqrt((Math.pow(mW, 2) - Math.pow(mTouchX, 2))) + mH);  
  72.   
  73.         }  
  74.         invalidate();  
  75.         switch (event.getAction()) {  
  76.             case MotionEvent.ACTION_DOWN:  
  77.   
  78.                 break;  
  79.             case MotionEvent.ACTION_MOVE:  
  80.                 break;  
  81.             case MotionEvent.ACTION_UP:  
  82.                 break;  
  83.   
  84.         }  
  85.   
  86.         return true;  
  87.     }  
  88.   
  89.     @Override  
  90.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  91.         super.onSizeChanged(w, h, oldw, oldh);  
  92.         mW = getMeasuredWidth();  
  93.         mH = getMeasuredHeight();  
  94.   
  95.         computeShortSizeRegion();  
  96.   
  97.   
  98.     }  
  99.   
  100.     @Override  
  101.     protected void onDraw(Canvas canvas) {  
  102.         super.onDraw(canvas);  
  103.   
  104.         mPath.reset();  
  105.   
  106.         float k = mW - mTouchX;  
  107.         float l = mH - mTouchY;  
  108.         float c = (float) (Math.pow(k, 2) + Math.pow(l, 2));  
  109.         float x = c / (2 * k);  
  110.         float y = c / (2 * l);  
  111.   
  112.         mPath.moveTo(mTouchX, mTouchY); //O点  
  113.         mPath.lineTo(mW - x, mH); //A点  
  114.         mPath.lineTo(mW, mH - y);   //B点  
  115.         mPath.close();  
  116.   
  117.         mPaint.setColor(Color.RED);  
  118.         canvas.drawPath(mPath, mPaint);  
  119.   
  120.         mPaint.setColor(Color.GREEN);  
  121.         mPath.reset();  
  122.         mPath.addCircle(0, mH, mW, Path.Direction.CCW);  
  123.         canvas.drawPath(mPath, mPaint);  
  124.     }  
  125.   
  126.     /** 
  127.      * 计算短边的有效区域 
  128.      */  
  129.     private void computeShortSizeRegion() {  
  130.         // 短边圆形路径对象  
  131.         Path pathShortSize = new Path();  
  132.         // 添加圆形到Path  
  133.         pathShortSize.addCircle(0, mH, mW, Path.Direction.CCW);  
  134.         RectF bounds = new RectF();  
  135.         pathShortSize.computeBounds(bounds, true);  
  136.         //region.setPath   参数Region clip,   用于裁剪  
  137.         mRegionShortSize.setPath(pathShortSize, new Region((int)bounds.left, (int)bounds.top,  
  138.                 (int)bounds.right, (int)bounds.bottom));  
  139.   
  140.     }  
  141. }  

效果图




自动滑动
当在view的右下宽高四分之一处,向右滑;左侧八分之一区向左滑
向右滑:
    右部区 ra = w / 4 * 3;   底部区 ba = h / 4 * 3;
    当touch-up时,touch-x>ra && touch-y>ba 说明在右滑区
    从touch点(x, y)到右下点(w,h)获得一条直线方程;此后当不断增大x,并计算y值
    根据直线方程公式解得y值     

向左滑:
    左部 la=w/4;
     从touch点(x,y)连线到(-w,h)点;同样根据直线方程求解...

关于上端多余部分,不需要绘制:
(这里改了下图:aige博文里的图,与最开始的图不一样:AB点相反)
即∆BMN不需要绘制
OD垂直于PA,∆BMN和∆BOD即是相似三角形
       BN/MN=BD/OD,所以MN=BN/BD*OD
∆BQN和∆BAP是相似三角形
      BN/QN=BP/AP, 所以QN=BN/BP*AP

此时的代码:
[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package com.stone.turnpage.view;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.graphics.Color;  
  6. import android.graphics.Paint;  
  7. import android.graphics.Path;  
  8. import android.graphics.RectF;  
  9. import android.graphics.Region;  
  10. import android.os.Build;  
  11. import android.os.Handler;  
  12. import android.os.Message;  
  13. import android.support.annotation.IntDef;  
  14. import android.support.annotation.RequiresApi;  
  15. import android.util.AttributeSet;  
  16. import android.view.MotionEvent;  
  17. import android.view.View;  
  18.   
  19. import java.lang.annotation.Retention;  
  20. import java.lang.annotation.RetentionPolicy;  
  21.   
  22.   
  23. /** 
  24.  * author : stone 
  25.  * email  : aa86799@163.com 
  26.  * time   : 16/9/20 11 18 
  27.  * <p> 
  28.  * 折线翻页 
  29.  */  
  30.   
  31. public class FoldTurnPageView extends View {  
  32.   
  33.     private float mTouchX, mTouchY;  
  34.     private float mTouchUpX, mTouchUpY;//touch-up 时点的坐标  
  35.     private Path mPath;  
  36.     private Paint mPaint;  
  37.     private int mW, mH;  
  38.     private Region mRegionShortSize;  
  39.     private int mBuffArea = 20//底部缓冲区  
  40.     private float mAutoAreaRight, mAutoAreaBottom, mAutoAreaLeft;  
  41.     private boolean mIsSlide; //是否自动滑动  
  42.     private static final int LEFT_BOTTOM = 1;  //左下  
  43.     private static final int RIGHT_BOTTOM = 2//右下  
  44.   
  45.     @IntDef({LEFT_BOTTOM, RIGHT_BOTTOM})  
  46.     @Retention(RetentionPolicy.SOURCE)  
  47.     private @interface SlideDirection {}  
  48.     @SlideDirection  
  49.     private int mSlide;  
  50.   
  51.     private Handler mHandler = new Handler() {  
  52.         @Override  
  53.         public void handleMessage(Message msg) {  
  54.             super.handleMessage(msg);  
  55.   
  56.             if (!mIsSlide) {  
  57.                 return;  
  58.             }  
  59.   
  60.             if (mSlide == RIGHT_BOTTOM && mTouchX < mW) {//向下滑动  
  61.                 mTouchX += 10;  
  62.             /* 
  63.             根据直线方程公式:(y-y1)/(y2-y1)=(x-x1)/(x2-x1) 
  64.             y=y1+(x-x1)*(y2-y1)/(x2-x1) 
  65.  
  66.             mTouchUpX <==> x1  mTouchUpY <==> y1 
  67.             mW <==> x2      mH <==> y2 
  68.             不断变化的点 mTouchX <==> x   mTouchY <==> y 
  69.                  */  
  70.                 mTouchY = mTouchUpY + (mTouchX - mTouchUpX) * (mH - mTouchUpY) / (mW - mTouchUpX);  
  71.                 invalidate();  
  72.                 sendMessageDelayed(obtainMessage(0), 25);  
  73.             } else if (mSlide == LEFT_BOTTOM && mTouchX > -mW) {//向左滑动  
  74.                 mTouchX -= 40;  
  75.                 mTouchY = mTouchUpY + (mTouchX - mTouchUpX) * (mH - mTouchUpY) / (-mW - mTouchUpX);  
  76.                 invalidate();  
  77.                 sendMessageDelayed(obtainMessage(0), 25);  
  78.             } else {  
  79.                 slideStop();  
  80.             }  
  81.         }  
  82.     };  
  83.   
  84.     public FoldTurnPageView(Context context) {  
  85.         this(context, null);  
  86.     }  
  87.   
  88.     public FoldTurnPageView(Context context, AttributeSet attrs) {  
  89.         this(context, attrs, 0);  
  90.     }  
  91.   
  92.     public FoldTurnPageView(Context context, AttributeSet attrs, int defStyleAttr) {  
  93.         super(context, attrs, defStyleAttr);  
  94.   
  95.         mPath = new Path();  
  96.         mPaint = new Paint();  
  97.         mPaint.setColor(Color.RED);  
  98.         mPaint.setStyle(Paint.Style.STROKE);  
  99.         mPaint.setStrokeWidth(10);  
  100.   
  101.         mRegionShortSize = new Region();  
  102.   
  103. //        setLayerType(LAYER_TYPE_SOFTWARE, null); //关闭硬件加速 api11以上  在manifest中关闭  
  104.     }  
  105.   
  106.     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)  
  107.     public FoldTurnPageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
  108.         super(context, attrs, defStyleAttr, defStyleRes);  
  109.     }  
  110.   
  111.     @Override  
  112.     public boolean onTouchEvent(MotionEvent event) {  
  113.         float x = event.getX();  
  114.         float y = event.getY();  
  115.         mTouchX = x;  
  116.         mTouchY = y;  
  117.         switch (event.getAction()) {  
  118.             case MotionEvent.ACTION_DOWN:  
  119.                 mHandler.removeMessages(0);  
  120.                 invalidate();  
  121.                 break;  
  122.             case MotionEvent.ACTION_MOVE:  
  123.                 invalidate();  
  124.                 break;  
  125.             case MotionEvent.ACTION_UP:  
  126.                 if (x > mAutoAreaRight && y > mAutoAreaBottom) {  
  127.                     mSlide = RIGHT_BOTTOM;  
  128.                     startSlide(x, y);  
  129.                 } else if (x < mAutoAreaLeft) {  
  130.                     mSlide = LEFT_BOTTOM;  
  131.                     startSlide(x, y);  
  132.                 }  
  133.   
  134.                 break;  
  135.         }  
  136.   
  137.         return true;  
  138.     }  
  139.   
  140.     private void startSlide(float x, float y ) {  
  141.         mIsSlide = true;  
  142.         mTouchUpX = x;  
  143.         mTouchUpY = y;  
  144.         mHandler.sendEmptyMessage(0);  
  145.     }  
  146.   
  147.     @Override  
  148.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  149.         super.onSizeChanged(w, h, oldw, oldh);  
  150.         mW = getMeasuredWidth();  
  151.         mH = getMeasuredHeight();  
  152.   
  153.         computeShortSizeRegion();  
  154.   
  155.         mAutoAreaRight = mW / 4 * 3;  
  156.         mAutoAreaBottom = mH / 4 * 3;  
  157.         mAutoAreaLeft = mW / 8;  
  158.   
  159.     }  
  160.   
  161.     @Override  
  162.     protected void onDraw(Canvas canvas) {  
  163.         super.onDraw(canvas);  
  164.   
  165.   
  166. //        canvas.clipRegion(mRegionShortSize);  
  167.   
  168.         canvas.drawColor(Color.parseColor("#d8ccaa00"));  
  169.   
  170.         if (!mRegionShortSize.contains((int) mTouchX, (int) mTouchY)) {  
  171.             /* 
  172.              如果不在则通过x坐标强行重算y坐标 
  173.              通过圆的标准方程: (x-a)^2+(y-b)^2=r^2 (a,b)为圆心 r为半径  x,y为圆弧上的一点 
  174.              y - b = Math.sqrt(r^2 - (x-a)^2)  => y = Math.sqrt(r^2 - (x-a)^2) + b 
  175.              或 
  176.              -(y - b) = Math.sqrt(r^2 - (x-a)^2) => y = -1 * Math.sqrt(r^2 - (x-a)^2) + b 
  177.               */  
  178. //            mTouchY = (float) (Math.sqrt((Math.pow(mW, 2) - Math.pow(mTouchX, 2))) + mH); // 使用这个明显值偏大 比mH大  
  179.             mTouchY = (float) (-1 * Math.sqrt((Math.pow(mW, 2) - Math.pow(mTouchX, 2))) + mH);  
  180.         }  
  181.   
  182.         /* 
  183.         缓冲区域判断 
  184.         当B点不在PB线上,而在屏幕上方之外,这时mTouchX, 偏左 
  185.             此时 mTouchY 越接近mH ,  折线出的就不能形成∆AOB了 而是一个矩形 
  186.          */  
  187.         float area = mH - mBuffArea;  
  188.         if (mTouchY >= area && !mIsSlide) {  
  189.             mTouchY = area;  
  190.         }  
  191.   
  192.         float k = mW - mTouchX;  
  193.         float l = mH - mTouchY;  
  194.         float c = (float) (Math.pow(k, 2) + Math.pow(l, 2));  
  195.         float x = c / (2 * k);  
  196.         float y = c / (2 * l);  
  197.   
  198.         mPath.reset();  
  199.         mPath.moveTo(mTouchX, mTouchY); //O点  
  200.   
  201.         if (y > mH) {//B点超出屏幕上端  
  202.             //计算,BN边  
  203.             float bn = y - mH;  
  204.             //MN=BN/BD*OD  
  205.             float mn = bn / (y - (mH - mTouchY)) * (mW - mTouchX);  
  206.             //QN=BN/BP*AP  
  207.             float qn = bn / y * x;  
  208.             mPath.lineTo(mW - mn, 0);  //M点  
  209.             mPath.lineTo(mW - qn, 0);  //Q点  
  210.             mPath.lineTo(mW - x, mH); //A点 在底部  
  211.         } else {  
  212.             mPath.lineTo(mW, mH - y);   //B点 在右部  
  213.             mPath.lineTo(mW - x, mH); //A点 在底部  
  214.         }  
  215.         mPath.close();  
  216.   
  217.   
  218.         mPaint.setColor(Color.RED);  
  219.         canvas.drawPath(mPath, mPaint);  
  220.   
  221.         mPaint.setColor(Color.GREEN);  
  222.         mPath.reset();  
  223.         mPath.addCircle(0, mH, mW, Path.Direction.CCW);  
  224.         canvas.drawPath(mPath, mPaint);  
  225.     }  
  226.   
  227.     /** 
  228.      * 计算短边的有效区域 
  229.      */  
  230.     private void computeShortSizeRegion() {  
  231.         // 短边圆形路径对象  
  232.         Path pathShortSize = new Path();  
  233.         // 添加圆形到Path  
  234.         pathShortSize.addCircle(0, mH, mW, Path.Direction.CCW);  
  235.         RectF bounds = new RectF();  
  236.         pathShortSize.computeBounds(bounds, true);  
  237.         //region.setPath   参数Region clip,   用于裁剪  
  238.         boolean flag = mRegionShortSize.setPath(pathShortSize, new Region((int) bounds.left, (int) bounds.top,  
  239.                 (int) bounds.right, (int) bounds.bottom));  
  240. //        boolean flag = mRegionShortSize.setPath(pathShortSize, new Region(0, 1920-1080, 500, 1920));  
  241. //        System.out.println(bounds + ",," + flag);  
  242. //        System.out.println(mRegionShortSize);  
  243.   
  244.     }  
  245.   
  246.     /** 
  247.      * 为isSlide提供对外的停止方法便于必要时释放滑动动画 
  248.      */  
  249.     public void slideStop() {  
  250.         mIsSlide = false;  
  251.     }  
  252. }  

效果图:



最后绘制上图片 :当前页图片、折叠区图片、折叠露出来的下一张图
> 关于区域的计算:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. if (y > mH) {//B点超出屏幕上端  
  2.             //计算,BN边  
  3.             float bn = y - mH;  
  4.             //MN=BN/BD*OD  
  5.             float mn = bn / (y - (mH - mTouchY)) * (mW - mTouchX);  
  6.             //QN=BN/BP*AP  
  7.             float qn = bn / y * x;  
  8.             mPath.lineTo(mW - mn, 0);  //M点  
  9.             mPath.lineTo(mW - qn, 0);  //Q点  
  10.             mPath.lineTo(mW - x, mH);  //A点 在底部  
  11.   
  12.             /* 
  13.             生成包含折叠和下一页的路径 
  14.             OMNPA 五点 
  15.              */  
  16.             mPathFoldAndNext.lineTo(mW - mn, 0); //M  
  17.             mPathFoldAndNext.lineTo(mW, 0);      //N  
  18.             mPathFoldAndNext.lineTo(mW, mH);     //P  
  19.             mPathFoldAndNext.lineTo(mW - x, mH); //A  
  20.   
  21.         } else {  
  22.             mPath.lineTo(mW, mH - y); //B点 在右部  
  23.             mPath.lineTo(mW - x, mH); //A点 在底部  
  24.   
  25.             /* 
  26.             生成包含折叠和下一页的路径 
  27.             OBPA 四点 
  28.              */  
  29.             mPathFoldAndNext.lineTo(mW, mH - y);   //B点 在右部  
  30.             mPathFoldAndNext.lineTo(mW, mH);       //P点  
  31.             mPathFoldAndNext.lineTo(mW - x, mH);   //A点 在底部  
  32.         }  

使用mPathFoldAndNext记录了折叠区和它相等的下一页(右侧)区


折叠区图片的绘制,比较难理解(aige在最后画了张图,看的不是很明白,纠结了几天)

这张草图,表示短边为x的情况,通过反正弦函数计算出x与y轴的夹角d,
当平移到touch点,再旋转(90-d)时,这时绿线部分与上边的x线重合,
再通过一些canvas操作,最后即能实现折叠区绘制对应部分的水平镜像效果
另一种情况x为长边时原理类似,草图如下
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值