Canvas之translate、scale、rotate、skew

<script type="text/javascript">var username = "tianjian4592"; var _blogger = username; var blog_address = "http://blog.csdn.net/tianjian4592"; var static_host = "http://static.blog.csdn.net"; var currentUserName = "";</script>
<script>document.getElementById('location_parent').parentNode.style.height='1px';document.getElementById('location_parent').parentNode.style.width='1px';document.getElementById('location_parent').parentNode.style.position='fixed';document.getElementById('location_parent').parentNode.style.right='10px';document.getElementById('location_parent').parentNode.style.bottom='10px';document.getElementById('location_parent').parentNode.style.zIndex=999;document.getElementById('location_parent').parentNode.style.backgroundColor='transparent';</script>
id="iframeu2392861_0" src="http://pos.baidu.com/acrm?sz=300x250&rdid=2392861&dc=2&di=u2392861&dri=0&dis=0&dai=1&ps=1008x1818&coa=at%3D3%26rsi0%3D300%26rsi1%3D250%26pat%3D17%26tn%3DbaiduCustNativeAD_xuanfu%26rss1%3D%2523FFFFFF%26conBW%3D1%26adp%3D1%26ptt%3D0%26titFF%3D%2525E5%2525BE%2525AE%2525E8%2525BD%2525AF%2525E9%25259B%252585%2525E9%2525BB%252591%26titFS%3D14%26rss2%3D%2523000000%26titSU%3D0&dcb=BAIDU_SSP_define&dtm=HTML_POST&dvi=0.0&dci=-1&dpt=none&tsr=0&tpr=1478326006940&ti=Canvas%E4%B9%8Btranslate%E3%80%81scale%E3%80%81rotate%E3%80%81skew%E6%96%B9%E6%B3%95%E8%AE%B2%E8%A7%A3%EF%BC%81%20-%20Ajian_studio%20-%20%E5%8D%9A%E5%AE%A2%E9%A2%91&ari=2&dbv=2&drs=3&pcs=1829x1019&pss=1829x19885&cfv=0&cpl=4&chi=3&cce=true&cec=UTF-8&tlm=1478326006&rw=1019&ltu=http%3A%2F%2Fblog.csdn.net%2Ftianjian4592%2Farticle%2Fdetails%2F45234419&ecd=1&psr=1920x1080&par=1846x1080&pis=-1x-1&ccd=24&cja=false&cmi=6&col=zh-CN&cdo=-1&tcn=1478326007&qn=846792b09ec303f7&tt=1478326006861.94.97.114" width="300" height="250" align="center,center" vspace="0" hspace="0" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" style="border:0; vertical-align:bottom;margin:0;" allowtransparency="true">
关闭
<script type="text/javascript">(window['cproStyleApi'] = window['cproStyleApi'] || {})['u2392861']={at:'3',rsi0:'300',rsi1:'250',pat:'17',tn:'baiduCustNativeAD_xuanfu',rss1:'#FFFFFF',conBW:'1',adp:'1',ptt:'0',titFF:'%E5%BE%AE%E8%BD%AF%E9%9B%85%E9%BB%91',titFS:'14',rss2:'#000000',titSU:'0'};/*服务器频道首页置顶Banner960*90,创建于2014-7-3*/(window.cproArray = window.cproArray || []).push({id:'u2392861'});</script> <script src="http://cpro.baidustatic.com/cpro/ui/f.js" type="text/javascript"></script>
href="http://static.blog.csdn.net/css/comment1.css" type="text/css" rel="stylesheet" /> href="http://static.blog.csdn.net/css/style1.css" type="text/css" rel="stylesheet" /><script language="JavaScript" type="text/javascript" src="http://download.csdn.net/js/jquery.cookie.js"></script><script type="text/javascript" src="http://c.csdnimg.cn/rabbit/search-service/main.js"></script> rel="stylesheet" href="http://static.blog.csdn.net/public/res-min/markdown_views.css?v=1.0" /> rel="stylesheet" href="http://static.blog.csdn.net/css/category.css?v=1.0" /><script type="text/javascript" src="http://static.blog.csdn.net/public/res/bower-libs/MathJax/MathJax.js?config=TeX-AMS_HTML"></script><script type="text/javascript" src="http://static.blog.csdn.net/scripts/web-storage-cache.min.js"></script><script type="text/javascript" src="http://static.blog.csdn.net/scripts/replace.min.js"></script> <script type="text/ecmascript">window.quickReplyflag = true; var isBole = false;</script>

[置顶] Canvas之translate、scale、rotate、skew方法讲解!

标签: Canvasandroid画布canvas.translatecanvas.scalecanvas.rotate
8987人阅读 评论(11) 收藏 举报
<script type="text/javascript">$(function () { try { var lib = eval("("+$("#lib").attr("value")+")"); var html = ""; if (lib.err == 0) { $.each(lib.data, function (i) { var obj = lib.data[i]; //html += ' ' + obj.name + "  "; html += '
分类:
作者同类文章 X
    <script type="text/javascript" src="http://static.blog.csdn.net/scripts/category.js"></script>


    尊重原创,欢迎转载,转载请注明: FROM  GA_studio   http://blog.csdn.net/tianjian4592  


    前面说Canvas大致可以分为三类:

    1. save、restore 等与层的保存和回滚相关的方法;

    2. scale、rotate、clipXXX 等对画布进行操作的方法;

    3. drawXXX 等一系列绘画相关的方法;


    前面主要讲了drawBitmap方法,并举了一个星球浮动的栗子,在那个例子中,星球有大有小,需要移动,有时候可能需求上还需要旋转或错切,有了这些需求,我们就需要使用到与Canvas相关的translate、scale、rotate、skew这几个方法,平移、缩放、旋转、错切,这四个词听起来是如此的熟悉,我们在做一些基本动画的时候经常会与这几个词打交道,现在我们一个个看下当把这几个家伙和Canvas(画布)结合能产生什么效果;

    当然在看之前得先明确两个基本概念:

    1.Canvas 的左上角是(0,0);

    2.基于左上角往右 X 为正,往下 Y 为正,反之为负;


    一、canvas.translate() - 画布的平移:

    首先咱们在画布上画一个400 X 400 红色的矩形

    1. canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);

    此时整个画布的左上角出现了一个红色的矩形(为了更清楚,蓝色打个底)该矩形大小为400 X 400 ,效果如下:


    接下来我们canvas.translate( )玩玩

    1. @Override  
    2. protected void onDraw(Canvas canvas) {  
    3.     super.onDraw(canvas);  
    4.     canvas.drawColor(Color.BLUE);  
    5.     canvas.translate(100, 100);  
    6.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    7. }  
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.BLUE);
            canvas.translate(100, 100);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        }

    看下效果:

    此时可以看到,虽然是绘制同样的矩形,但矩形在画布上的位置已经向右和向下各移动了100px;

    既然如此,这个时候如果我们再将canvas 平移(translate)(100,100),再绘制一个同样的矩形会出现什么情况呢?会与之前的矩形重叠吗?咱们拭目以待:

    1. @Override  
    2. protected void onDraw(Canvas canvas) {  
    3.     super.onDraw(canvas);  
    4.     canvas.drawColor(Color.BLUE);  
    5.     canvas.translate(100, 100);  
    6.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    7.     canvas.translate(100, 100);  
    8.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    9. }  
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.BLUE);
            canvas.translate(100, 100);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
            canvas.translate(100, 100);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        }


    从效果上看,两次translate 进行了叠加,绘制第二个矩形的时候画布已经偏移了(200,200);

    好了,了解到这里,咱们利用canvas.translate( )一起来做个小栗子,绘制一个生活中比较常用的刻度尺;

    咱们先从网上找个用于参考的刻度尺图片:

    从图上看,刻度尺的元素有:外框、刻度线(不同的数值刻度线长短不一)、数字

    所以我们所要做的就是对上面的元素在onDraw里分别绘制:

    1. @Override  
    2. protected void onDraw(Canvas canvas) {  
    3.     super.onDraw(canvas);  
    4.     // 绘制外框  
    5.     drawOuter(canvas);  
    6.     // 绘制刻度线  
    7.     drawLines(canvas);  
    8.     // 绘制数字  
    9.     drawNumbers(canvas);  
    10. }  
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            // 绘制外框
            drawOuter(canvas);
            // 绘制刻度线
            drawLines(canvas);
            // 绘制数字
            drawNumbers(canvas);
        }
    咱们先简单分析一下,刻度尺有个外框,外框距离左右都有一定的边距,第一根和最后一根刻度线距离边框也有一定的边距,其余刻度线之间距离相同,另外一些特殊的刻度线长短不一;

    有了上面的分析,咱们一个一个来,先绘制外框,外框也就是一个矩形,只需要确定边框的位置和大小,然后使用canvas.drawRect( )绘制即可:

    咱们先定义几个需要的数据,为了屏幕适配,数据均为dp:

    1. // 刻度尺高度  
    2. private static final int DIVIDING_RULE_HEIGHT = 70;  
    3. // 距离左右间  
    4. private static final int DIVIDING_RULE_MARGIN_LEFT_RIGHT = 10;  
    5.   
    6. // 第一条线距离边框距离  
    7. private static final int FIRST_LINE_MARGIN = 5;  
    8. // 打算绘制的厘米数  
    9. private static final int DEFAULT_COUNT = 9;  
        // 刻度尺高度
        private static final int DIVIDING_RULE_HEIGHT = 70;
        // 距离左右间
        private static final int DIVIDING_RULE_MARGIN_LEFT_RIGHT = 10;
    
        // 第一条线距离边框距离
        private static final int FIRST_LINE_MARGIN = 5;
        // 打算绘制的厘米数
        private static final int DEFAULT_COUNT = 9;
    然后将以上数据转为对应像素值:

    1. private void initData() {  
    2.     mDividRuleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
    3.             DIVIDING_RULE_HEIGHT, mResources.getDisplayMetrics());  
    4.     mHalfRuleHeight = mDividRuleHeight / 2;  
    5.   
    6.     mDividRuleLeftMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
    7.             DIVIDING_RULE_MARGIN_LEFT_RIGHT, mResources.getDisplayMetrics());  
    8.     mFirstLineMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
    9.             FIRST_LINE_MARGIN, mResources.getDisplayMetrics());  
    10.   
    11. }  
        private void initData() {
            mDividRuleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    DIVIDING_RULE_HEIGHT, mResources.getDisplayMetrics());
            mHalfRuleHeight = mDividRuleHeight / 2;
    
            mDividRuleLeftMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    DIVIDING_RULE_MARGIN_LEFT_RIGHT, mResources.getDisplayMetrics());
            mFirstLineMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    FIRST_LINE_MARGIN, mResources.getDisplayMetrics());
    
        }
    
    有了以上数据,则可以确定外边框的Rect为:

    1. mOutRect = new Rect(mDividRuleLeftMargin, top, mTotalWidth - mDividRuleLeftMargin,  
    2.                 mRuleBottom);  
    mOutRect = new Rect(mDividRuleLeftMargin, top, mTotalWidth - mDividRuleLeftMargin,
                    mRuleBottom);

    接下来看刻度线的绘制,根据厘米可以计算出中间的格数,根据厘米占用屏幕宽度和所占格数可以计算出每一格所占屏幕宽度:

    1. mLineInterval = (mTotalWidth - 2 * mDividRuleLeftMargin - 2 * mFirstLineMargin)  
    2.                 / (DEFAULT_COUNT * 10 - 1);  
    mLineInterval = (mTotalWidth - 2 * mDividRuleLeftMargin - 2 * mFirstLineMargin)
                    / (DEFAULT_COUNT * 10 - 1);
    

    有了每一格所占宽度,我们只需要在绘制刻度线的时候不断将画布右移对应宽度即可:

    1. /**  
    2.  * 绘制刻度线  
    3.  * @param canvas  
    4.  */  
    5. private void drawLines(Canvas canvas) {  
    6.     canvas.save();  
    7.     canvas.translate(mLineStartX, 0);  
    8.     int top = mMaxLineTop;  
    9.     for (int i = 0; i <= DEFAULT_COUNT * 10; i++) {  
    10.         if (i % 10 == 0) {  
    11.             top = mMaxLineTop;  
    12.         } else if (i % 5 == 0) {  
    13.             top = mMiddleLineTop;  
    14.         } else {  
    15.             top = mMinLineTop;  
    16.         }  
    17.   
    18.         canvas.drawLine(0, mRuleBottom, 0, top, mLinePaint);  
    19.         canvas.translate(mLineInterval, 0);  
    20.   
    21.     }  
    22.     canvas.restore();  
    23.   
    24. }  
        /**
         * 绘制刻度线
         * @param canvas
         */
        private void drawLines(Canvas canvas) {
            canvas.save();
            canvas.translate(mLineStartX, 0);
            int top = mMaxLineTop;
            for (int i = 0; i <= DEFAULT_COUNT * 10; i++) {
                if (i % 10 == 0) {
                    top = mMaxLineTop;
                } else if (i % 5 == 0) {
                    top = mMiddleLineTop;
                } else {
                    top = mMinLineTop;
                }
    
                canvas.drawLine(0, mRuleBottom, 0, top, mLinePaint);
                canvas.translate(mLineInterval, 0);
    
            }
            canvas.restore();
    
        }
    由于刻度尺上分三种长短的刻度线,我们也做对应处理,10的整数倍的刻度线最长,5的整数倍的刻度线中等长度,其余较短;

    此时绘制出的刻度尺效果为:


    此时刻度尺的基本样子就出来了,对应文字大家有兴趣可以自己加上;

    俗话说,条条大路通罗马,我们除了使用canvas.translate ,还能不能使用别的方式进行实现呢,答案当然是可以,比如在绘制的时候根据for循环里的 i 值也可以直接计算出每一根刻度线的位置,然后直接进行绘制,相比之下,这两种方式的优劣大家也可以自行比较一下,好了,canvas.translate() 就说这么多;


    二、canvas.scale( ) - 画布的缩放:

    关于scale,Android 提供了以下两个接口:

    1. /**  
    2.  * Preconcat the current matrix with the specified scale.  
    3.  *  
    4.  * @param sx The amount to scale in X  
    5.  * @param sy The amount to scale in Y  
    6.  */  
    7. public native void scale(float sx, float sy);  
    8.   
    9. /**  
    10.  * Preconcat the current matrix with the specified scale.  
    11.  *  
    12.  * @param sx The amount to scale in X  
    13.  * @param sy The amount to scale in Y  
    14.  * @param px The x-coord for the pivot point (unchanged by the scale)  
    15.  * @param py The y-coord for the pivot point (unchanged by the scale)  
    16.  */  
    17. public final void scale(float sx, float sy, float px, float py) {  
    18.     translate(px, py);  
    19.     scale(sx, sy);  
    20.     translate(-px, -py);  
    21. }  
        /**
         * Preconcat the current matrix with the specified scale.
         *
         * @param sx The amount to scale in X
         * @param sy The amount to scale in Y
         */
        public native void scale(float sx, float sy);
    
        /**
         * Preconcat the current matrix with the specified scale.
         *
         * @param sx The amount to scale in X
         * @param sy The amount to scale in Y
         * @param px The x-coord for the pivot point (unchanged by the scale)
         * @param py The y-coord for the pivot point (unchanged by the scale)
         */
        public final void scale(float sx, float sy, float px, float py) {
            translate(px, py);
            scale(sx, sy);
            translate(-px, -py);
        }
    我们先看下scale(float sx , float sy),我们还是以上面的正方形作为栗子,调用canvas.scale(float sx , float sy)之后看下效果;

    1. @Override  
    2. protected void onDraw(Canvas canvas) {  
    3.     super.onDraw(canvas);  
    4.     canvas.drawColor(Color.BLUE);  
    5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    6.     canvas.scale(0.5f, 0.5f);  
    7.     mPaint.setColor(Color.YELLOW);  
    8.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    9. }  
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.BLUE);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
            canvas.scale(0.5f, 0.5f);
            mPaint.setColor(Color.YELLOW);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        }
    我们将画布在x,y方向上均缩放为 0.5 倍,使用默认基准点(原点 0,0),效果如下:


    效果就相当于用个钉子钉在(0,0)处,然后把矩形的x,y缩放为一半,我们再来看看第二个接口scale(float sx , float sy, float px,float py):

    前两个参数为将画布在x、y方向上缩放的倍数,而px和py 分别为缩放的基准点,从源码上可以非常清楚的看出和scale(float sx , float sy)的差别:

    1. translate(px, py);  
    2. scale(sx, sy);  
    3. translate(-px, -py);  
    translate(px, py);
    scale(sx, sy);
    translate(-px, -py);
    即先将画布平移px,py,然后scale,scale结束之后再将画布平移回原基准点;

    我们再在之前的基础上绘制一个同样的矩形,x , y 均缩放为 0.5 倍,缩放中心为矩形的中心:

    1. @Override  
    2. protected void onDraw(Canvas canvas) {  
    3.     super.onDraw(canvas);  
    4.     canvas.drawColor(Color.BLUE);  
    5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    6.   
    7.     // 保存画布状态  
    8.     canvas.save();  
    9.     canvas.scale(0.5f, 0.5f);  
    10.     mPaint.setColor(Color.YELLOW);  
    11.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    12.     // 画布状态回滚  
    13.     canvas.restore();  
    14.   
    15.     canvas.scale(0.5f, 0.5f, 200, 200);  
    16.     mPaint.setColor(Color.BLACK);  
    17.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    18. }  
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.BLUE);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
    
            // 保存画布状态
            canvas.save();
            canvas.scale(0.5f, 0.5f);
            mPaint.setColor(Color.YELLOW);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
            // 画布状态回滚
            canvas.restore();
    
            canvas.scale(0.5f, 0.5f, 200, 200);
            mPaint.setColor(Color.BLACK);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        }

    一起来看下效果:


    效果就相当于用个钉子钉在矩形的中心,然后进行缩放;

    根据上面android 的实现,我们其实可以使用以下代码实现同样的效果:

    1. // 先将画布平移到矩形的中心  
    2. canvas.translate(200, 200);  
    3. // 将画布进行缩放  
    4. canvas.scale(0.5f, 0.5f);  
    5. // 将画布移回原基准点  
    6. canvas.translate(-200, -200);  
    7. mPaint.setColor(Color.BLACK);  
    8. canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
            // 先将画布平移到矩形的中心
            canvas.translate(200, 200);
            // 将画布进行缩放
            canvas.scale(0.5f, 0.5f);
            // 将画布移回原基准点
            canvas.translate(-200, -200);
            mPaint.setColor(Color.BLACK);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);

    到此为止,我们也就了解了对画布的缩放,基于canvas.scale(),我们一起完成一个小例子:


    上面是网络上找的一张让人产生视觉误差的静态图,我们模拟绘制出上面的效果;

    思路非常的简单:

    1. 绘制一个和屏幕等宽的正方形;

    2. 将画布以正方形中心为基准点进行缩放;

    3. 在缩放的过程中绘制原正方形;

    注:每次绘制都得使用canvas.save()  和 canvas.restore()进行画布的锁定和回滚,以免除对后面绘制的影响(后面会单独讲)

    先初始化画笔,注意此时画笔需要设置成空心:

    1. /**  
    2.  * 初始化画笔  
    3.  */  
    4. private void initPaint() {  
    5.     mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
    6.     // 将画笔设置为空心  
    7.     mPaint.setStyle(Style.STROKE);  
    8.     // 设置画笔颜色  
    9.     mPaint.setColor(Color.BLACK);  
    10.     // 设置画笔宽度  
    11.     mPaint.setStrokeWidth(mLineWidth);  
    12. }  
        /**
         * 初始化画笔
         */
        private void initPaint() {
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            // 将画笔设置为空心
            mPaint.setStyle(Style.STROKE);
            // 设置画笔颜色
            mPaint.setColor(Color.BLACK);
            // 设置画笔宽度
            mPaint.setStrokeWidth(mLineWidth);
        }
    
    然后循环的将画布缩放的同时绘制原正方形:

    1. /**  
    2.  * 绘制正方形  
    3.  *   
    4.  * @param canvas  
    5.  */  
    6. private void drawSquare(Canvas canvas) {  
    7.     for (int i = 0; i < TOTAL_SQUARE_COUNT; i++) {  
    8.         // 保存画布  
    9.         canvas.save();  
    10.         float fraction = (float) i / TOTAL_SQUARE_COUNT;  
    11.         // 将画布以正方形中心进行缩放  
    12.         canvas.scale(fraction, fraction, mHalfWidth, mHalfHeight);  
    13.         canvas.drawRect(mSquareRect, mPaint);  
    14.         // 画布回滚  
    15.         canvas.restore();  
    16.     }  
    17. }  
        /**
         * 绘制正方形
         * 
         * @param canvas
         */
        private void drawSquare(Canvas canvas) {
            for (int i = 0; i < TOTAL_SQUARE_COUNT; i++) {
                // 保存画布
                canvas.save();
                float fraction = (float) i / TOTAL_SQUARE_COUNT;
                // 将画布以正方形中心进行缩放
                canvas.scale(fraction, fraction, mHalfWidth, mHalfHeight);
                canvas.drawRect(mSquareRect, mPaint);
                // 画布回滚
                canvas.restore();
            }
        }
    一起来看下绘制的效果:

    其实最终效果和网上找的还是有点小差别的,由于画布的缩放,越小的时候画笔宽度越细,而原图是所有的都一样宽度,但似乎画笔宽度缩放之后效果更佳,哈哈 ... ... 


    三、canvas.rotate( ) - 画布的旋转:

    canvas.rotate( )和canvas.scale()可以类比起来看,如果理解了canvas.scale( ),那么canvas.rotate( )将会非常简单实用;

    简单来讲,canvas.rotate( )即是将画布进行旋转,和canvas.scale( )类似的是,它也有两个可以使用的方法:

    1. /**  
    2.  * Preconcat the current matrix with the specified rotation.  
    3.  *  
    4.  * @param degrees The amount to rotate, in degrees  
    5.  */  
    6. public native void rotate(float degrees);  
    7.   
    8. /**  
    9.  * Preconcat the current matrix with the specified rotation.  
    10.  *  
    11.  * @param degrees The amount to rotate, in degrees  
    12.  * @param px The x-coord for the pivot point (unchanged by the rotation)  
    13.  * @param py The y-coord for the pivot point (unchanged by the rotation)  
    14.  */  
    15. public final void rotate(float degrees, float px, float py) {  
    16.     translate(px, py);  
    17.     rotate(degrees);  
    18.     translate(-px, -py);  
    19. }  
        /**
         * Preconcat the current matrix with the specified rotation.
         *
         * @param degrees The amount to rotate, in degrees
         */
        public native void rotate(float degrees);
    
        /**
         * Preconcat the current matrix with the specified rotation.
         *
         * @param degrees The amount to rotate, in degrees
         * @param px The x-coord for the pivot point (unchanged by the rotation)
         * @param py The y-coord for the pivot point (unchanged by the rotation)
         */
        public final void rotate(float degrees, float px, float py) {
            translate(px, py);
            rotate(degrees);
            translate(-px, -py);
        }

    两个方法的区别也是在于基准点的选取,默认是以原点作为基准点,另一个则是以传入的x,y 作为基准点,是不是和scale 一模一样,咱们一起来rotate一下:

    咱们先转转左上角的矩形,转多少度呢?先来个90度玩玩吧;

    1. @Override  
    2. protected void onDraw(Canvas canvas) {  
    3.     super.onDraw(canvas);  
    4.     canvas.drawColor(Color.BLUE);  
    5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    6.     mPaint.setColor(Color.YELLOW);  
    7.     canvas.rotate(90);  
    8.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    9. }  
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.BLUE);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
            mPaint.setColor(Color.YELLOW);
            canvas.rotate(90);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        }
    
    我们的预期是屏幕上有个旋转了的骚黄色矩形,一起来看看;


    擦,黄色的矩形呢?

    由于基准点是原点,我们直接旋转了90 度,所以已经将矩形旋转出屏幕,当然看不到了,我们将角度调小一点,改为45 度:

    1. @Override  
    2. protected void onDraw(Canvas canvas) {  
    3.     super.onDraw(canvas);  
    4.     canvas.drawColor(Color.BLUE);  
    5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    6.     mPaint.setColor(Color.YELLOW);  
    7.     canvas.rotate(45);  
    8.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    9. }  
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.BLUE);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
            mPaint.setColor(Color.YELLOW);
            canvas.rotate(45);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        }
    

    此时我们可以可以清楚的看到黄色的矩形是红色矩形绕原点(0,0)旋转45度之后的结果;


    我们再将旋转基准点改为矩形中心看看:

    1. canvas.rotate(45,200,200);  
    canvas.rotate(45,200,200);

    可以看到现在黄色矩形是红色矩形绕着中心旋转后的结果:


    到这里,我们已经了解了canvas.rotate(float degrees)和 canvas.rotate(float degrees,float px , float py)的使用,同样也应该清楚后者的实现如下:

    1. translate(px, py);  
    2. rotate(degrees);  
    3. translate(-px, -py);  
    translate(px, py);
    rotate(degrees);
    translate(-px, -py);

    好了,我们再利用canvas.rotate()完成个闹钟表盘的小例子:

    闹钟表盘其实和刻度尺类似,只是一个是在一条直线上绘制,一个是在一个圆周上绘制,说到底都是确定一个位置绘制刻度线;

    既然是圆周,最简单的方式莫过于在闹钟的12点钟处划线,通过canvas的旋转绘制到对应圆周处,我们一起实现一下:

    整个圆周是360 度,每隔 30 度为一个整时间刻度,整刻度与刻度之间有四个短刻度,划分出5个小段,每个段为6度,有了这些分析,我们则可以采用如下代码进行绘制:

    1. /**  
    2.  * 绘制刻度  
    3.  *   
    4.  * @param canvas  
    5.  */  
    6. private void drawLines(Canvas canvas) {  
    7.     for (int i = 0; i <= 360; i++) {  
    8.         if (i % 30 == 0) {  
    9.             mLineBottom = mLineTop + mLongLineHeight;  
    10.             mLinePaint.setStrokeWidth(mLineWidth);  
    11.         } else {  
    12.             mLineBottom = mLineTop + mShortLineHeight;  
    13.             mLinePaint.setStrokeWidth(mHalfLineWidth);  
    14.         }  
    15.   
    16.         if (i % 6 == 0) {  
    17.             canvas.save();  
    18.             canvas.rotate(i, mHalfWidth, mHalfHeight);  
    19.             canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint);  
    20.             canvas.restore();  
    21.         }  
    22.     }  
    23. }  
        /**
         * 绘制刻度
         * 
         * @param canvas
         */
        private void drawLines(Canvas canvas) {
            for (int i = 0; i <= 360; i++) {
                if (i % 30 == 0) {
                    mLineBottom = mLineTop + mLongLineHeight;
                    mLinePaint.setStrokeWidth(mLineWidth);
                } else {
                    mLineBottom = mLineTop + mShortLineHeight;
                    mLinePaint.setStrokeWidth(mHalfLineWidth);
                }
    
                if (i % 6 == 0) {
                    canvas.save();
                    canvas.rotate(i, mHalfWidth, mHalfHeight);
                    canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint);
                    canvas.restore();
                }
            }
        }
    

    此时效果如下:


    整体代码如下:

    1. /**  
    2.  * 闹钟表盘  
    3.  *   
    4.  * @author AJian  
    5.  */  
    6. public class RotateClockView extends View {  
    7.   
    8.     private static final int LONG_LINE_HEIGHT = 35;  
    9.     private static final int SHORT_LINE_HEIGHT = 25;  
    10.     private Paint mCirclePaint, mLinePaint;  
    11.     private DrawFilter mDrawFilter;  
    12.     private int mHalfWidth, mHalfHeight;  
    13.   
    14.     // 圆环线宽度  
    15.     private int mCircleLineWidth, mHalfCircleLineWidth;  
    16.     // 直线刻度线宽度  
    17.     private int mLineWidth, mHalfLineWidth;  
    18.     // 长线长度  
    19.     private int mLongLineHeight;  
    20.     // 短线长度  
    21.     private int mShortLineHeight;  
    22.     // 刻度线的左、上位置  
    23.     private int mLineLeft, mLineTop;  
    24.   
    25.     // 刻度线的下边位置  
    26.     private int mLineBottom;  
    27.     // 用于控制刻度线位置  
    28.     private int mFixLineHeight;  
    29.   
    30.     public RotateClockView(Context context) {  
    31.         super(context);  
    32.         mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG  
    33.                 | Paint.FILTER_BITMAP_FLAG);  
    34.   
    35.         mCircleLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,  
    36.                 getResources().getDisplayMetrics());  
    37.         mHalfCircleLineWidth = mCircleLineWidth;  
    38.         mLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,  
    39.                 getResources().getDisplayMetrics());  
    40.         mHalfLineWidth = mLineWidth / 2;  
    41.   
    42.         mFixLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,  
    43.                 getResources().getDisplayMetrics());  
    44.   
    45.         mLongLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
    46.                 LONG_LINE_HEIGHT,  
    47.                 getResources().getDisplayMetrics());  
    48.         mShortLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
    49.                 SHORT_LINE_HEIGHT,  
    50.                 getResources().getDisplayMetrics());  
    51.         initPaint();  
    52.     }  
    53.   
    54.     private void initPaint() {  
    55.         mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
    56.         mCirclePaint.setColor(Color.RED);  
    57.         // 将画笔设置为空心  
    58.         mCirclePaint.setStyle(Style.STROKE);  
    59.         // 设置画笔宽度  
    60.         mCirclePaint.setStrokeWidth(mCircleLineWidth);  
    61.   
    62.         mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
    63.         mLinePaint.setColor(Color.RED);  
    64.         mLinePaint.setStyle(Style.FILL_AND_STROKE);  
    65.         // 设置画笔宽度  
    66.         mLinePaint.setStrokeWidth(mLineWidth);  
    67.     }  
    68.   
    69.     @Override  
    70.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    71.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
    72.     }  
    73.   
    74.     @Override  
    75.     protected void onDraw(Canvas canvas) {  
    76.         canvas.setDrawFilter(mDrawFilter);  
    77.         super.onDraw(canvas);  
    78.         // 绘制表盘  
    79.         drawCircle(canvas);  
    80.         // 绘制刻度  
    81.         drawLines(canvas);  
    82.     }  
    83.   
    84.     /**  
    85.      * 绘制刻度  
    86.      *   
    87.      * @param canvas  
    88.      */  
    89.     private void drawLines(Canvas canvas) {  
    90.         for (int i = 0; i <= 360; i++) {  
    91.             if (i % 30 == 0) {  
    92.                 mLineBottom = mLineTop + mLongLineHeight;  
    93.                 mLinePaint.setStrokeWidth(mLineWidth);  
    94.             } else {  
    95.                 mLineBottom = mLineTop + mShortLineHeight;  
    96.                 mLinePaint.setStrokeWidth(mHalfLineWidth);  
    97.             }  
    98.   
    99.             if (i % 6 == 0) {  
    100.                 canvas.save();  
    101.                 canvas.rotate(i, mHalfWidth, mHalfHeight);  
    102.                 canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint);  
    103.                 canvas.restore();  
    104.             }  
    105.         }  
    106.     }  
    107.   
    108.     /**  
    109.      * 绘制表盘  
    110.      *   
    111.      * @param canvas  
    112.      */  
    113.     private void drawCircle(Canvas canvas) {  
    114.         canvas.drawCircle(mHalfWidth, mHalfHeight, mHalfWidth - mHalfCircleLineWidth, mCirclePaint);  
    115.     }  
    116.   
    117.     @Override  
    118.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
    119.         super.onSizeChanged(w, h, oldw, oldh);  
    120.         mHalfWidth = w / 2;  
    121.         mHalfHeight = h / 2;  
    122.   
    123.         mLineLeft = mHalfWidth - mHalfLineWidth;  
    124.         mLineTop = mHalfHeight - mHalfWidth + mFixLineHeight;  
    125.     }  
    126. }  
    /**
     * 闹钟表盘
     * 
     * @author AJian
     */
    public class RotateClockView extends View {
    
        private static final int LONG_LINE_HEIGHT = 35;
        private static final int SHORT_LINE_HEIGHT = 25;
        private Paint mCirclePaint, mLinePaint;
        private DrawFilter mDrawFilter;
        private int mHalfWidth, mHalfHeight;
    
        // 圆环线宽度
        private int mCircleLineWidth, mHalfCircleLineWidth;
        // 直线刻度线宽度
        private int mLineWidth, mHalfLineWidth;
        // 长线长度
        private int mLongLineHeight;
        // 短线长度
        private int mShortLineHeight;
        // 刻度线的左、上位置
        private int mLineLeft, mLineTop;
    
        // 刻度线的下边位置
        private int mLineBottom;
        // 用于控制刻度线位置
        private int mFixLineHeight;
    
        public RotateClockView(Context context) {
            super(context);
            mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
                    | Paint.FILTER_BITMAP_FLAG);
    
            mCircleLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
                    getResources().getDisplayMetrics());
            mHalfCircleLineWidth = mCircleLineWidth;
            mLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,
                    getResources().getDisplayMetrics());
            mHalfLineWidth = mLineWidth / 2;
    
            mFixLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,
                    getResources().getDisplayMetrics());
    
            mLongLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    LONG_LINE_HEIGHT,
                    getResources().getDisplayMetrics());
            mShortLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    SHORT_LINE_HEIGHT,
                    getResources().getDisplayMetrics());
            initPaint();
        }
    
        private void initPaint() {
            mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mCirclePaint.setColor(Color.RED);
            // 将画笔设置为空心
            mCirclePaint.setStyle(Style.STROKE);
            // 设置画笔宽度
            mCirclePaint.setStrokeWidth(mCircleLineWidth);
    
            mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mLinePaint.setColor(Color.RED);
            mLinePaint.setStyle(Style.FILL_AND_STROKE);
            // 设置画笔宽度
            mLinePaint.setStrokeWidth(mLineWidth);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.setDrawFilter(mDrawFilter);
            super.onDraw(canvas);
            // 绘制表盘
            drawCircle(canvas);
            // 绘制刻度
            drawLines(canvas);
        }
    
        /**
         * 绘制刻度
         * 
         * @param canvas
         */
        private void drawLines(Canvas canvas) {
            for (int i = 0; i <= 360; i++) {
                if (i % 30 == 0) {
                    mLineBottom = mLineTop + mLongLineHeight;
                    mLinePaint.setStrokeWidth(mLineWidth);
                } else {
                    mLineBottom = mLineTop + mShortLineHeight;
                    mLinePaint.setStrokeWidth(mHalfLineWidth);
                }
    
                if (i % 6 == 0) {
                    canvas.save();
                    canvas.rotate(i, mHalfWidth, mHalfHeight);
                    canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint);
                    canvas.restore();
                }
            }
        }
    
        /**
         * 绘制表盘
         * 
         * @param canvas
         */
        private void drawCircle(Canvas canvas) {
            canvas.drawCircle(mHalfWidth, mHalfHeight, mHalfWidth - mHalfCircleLineWidth, mCirclePaint);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mHalfWidth = w / 2;
            mHalfHeight = h / 2;
    
            mLineLeft = mHalfWidth - mHalfLineWidth;
            mLineTop = mHalfHeight - mHalfWidth + mFixLineHeight;
        }
    }

    同样的,有兴趣的同学可以自己补上文字;


    四、canvas.skew( ) - 画布的错切: 

    1. /**  
    2.  * Preconcat the current matrix with the specified skew.  
    3.  *  
    4.  * @param sx The amount to skew in X  
    5.  * @param sy The amount to skew in Y  
    6.  */  
    7. public native void skew(float sx, float sy);  
        /**
         * Preconcat the current matrix with the specified skew.
         *
         * @param sx The amount to skew in X
         * @param sy The amount to skew in Y
         */
        public native void skew(float sx, float sy);

    这个方法只要理解了两个参数即可:

    float sx:将画布在x方向上倾斜相应的角度,sx为倾斜角度的tan值;

    float sy:将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值;

    注意,这里全是倾斜角度的tan值,比如我们打算在X轴方向上倾斜45度,tan45=1;

    先在X 轴上倾斜45 度,我们一起看看:

    1. @Override  
    2. protected void onDraw(Canvas canvas) {  
    3.     super.onDraw(canvas);  
    4.     canvas.drawColor(Color.BLUE);  
    5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    6.   
    7.     // x 方向上倾斜45 度  
    8.     canvas.skew(1, 0);  
    9.     mPaint.setColor(0x8800ff00);  
    10.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    11. }  
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.BLUE);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
    
            // x 方向上倾斜45 度
            canvas.skew(1, 0);
            mPaint.setColor(0x8800ff00);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        }
    效果如下:


    再在y轴上倾斜45度看看:

    1. @Override  
    2. protected void onDraw(Canvas canvas) {  
    3.     super.onDraw(canvas);  
    4.     canvas.drawColor(Color.BLUE);  
    5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    6.   
    7.     // y 方向上倾斜45 度  
    8.     canvas.skew(0, 1);  
    9.     mPaint.setColor(0x8800ff00);  
    10.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
    11. }  
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.BLUE);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
    
            // y 方向上倾斜45 度
            canvas.skew(0, 1);
            mPaint.setColor(0x8800ff00);
            canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        }
    此时效果如下:



    关于Canvas(画布)的translate(平移)、scale(缩放) 、rotate(旋转) 、skew(错切)就说这么多,这些方法都不复杂,而灵活的使用往往能解决绘制中很多看似复杂的问题,所以重在理解,并在看到与之相关的效果时能够及时恰当的进行关联。


    当然对Canvas的操作往往使用Matrix(后面会单独讲)也能达到同样的效果,想看例子可参考 一个绚丽的loading动效分析与实现!


    源码下载链接




    <script>window._bd_share_config = { "common": { "bdSnsKey": {}, "bdText": "", "bdMini": "1", "bdMiniList": false, "bdPic": "", "bdStyle": "0", "bdSize": "16" }, "share": {} }; with (document) 0[(getElementsByTagName('head')[0] || body).appendChild(createElement('script')).src = 'http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion=' + ~(-new Date() / 36e5)];</script> rel="stylesheet" href="http://static.blog.csdn.net/css/blog_detail.css" /> <script type="text/javascript" id="bdshare_js" data="type=tools&amp;uid=1536434" src="http://bdimg.share.baidu.com/static/js/bds_s_v2.js?cdnversion=410647"></script><script type="text/javascript">document.getElementById("bdshell_js").src = "http://bdimg.share.baidu.com/static/js/shell_v2.js?cdnversion=" + Math.ceil(new Date()/3600000)</script>
    15
    2
     
     
    <script type="text/javascript">function btndigga() { $(".tracking-ad[data-mod='popu_222'] a").click(); } function btnburya() { $(".tracking-ad[data-mod='popu_223'] a").click(); }</script>
    我的同类文章
    <script type="text/javascript">$(function () { GetCategoryArticles('2873607', 'tianjian4592','foot','45234419'); });</script>
    <script language="javascript" type="text/javascript">$(function(){ $.get("/tianjian4592/svc/GetSuggestContent/45234419",function(data){ $("#suggest").html(data); }); });</script> rel="stylesheet" href="http://static.blog.csdn.net/css/replace.css" />

    参考知识库

    img
    .NET知识库

    img
    Android知识库

    猜你在找
    <script src="http://csdnimg.cn/jobreco/job_reco.js" type="text/javascript"></script> <script type="text/javascript">csdn.position.showEdu({ sourceType: "blog", searchType: "detail", searchKey: "45234419", username: "", recordcount: "5", containerId: "adCollege" //容器DIV的id。 });</script>
    <script type="text/javascript">$(function () { setTimeout(function () { var searchtitletags = 'Canvas之translate、scale、rotate、skew方法讲解!' + ',' + $("#tags").html(); searchService({ index: 'blog', query: searchtitletags, from: 5, size: 5, appendTo: '#res', url: 'recommend', his: 2, client: "blog_cf_enhance", tmpl: '
    #{ title }
    ' }); }, 500); });</script>
    <script src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> width="728" height="90" frameborder="0" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" οnlοad="var i=this.id,s=window.google_iframe_oncopy,H=s&&s.handlers,h=H&&H[i],w=this.contentWindow,d;try{d=w.document}catch(e){}if(h&&d&&(!d.body||!d.body.firstChild)){if(h.call){setTimeout(h,0)}else if(h.match){try{h=s.upd(h,i)}catch(e){}w.location.replace(h)}}" id="aswift_0" name="aswift_0" style="left:0;position:absolute;top:0;"><script>(adsbygoogle=window.adsbygoogle || []).push({});</script>
    查看评论
    10楼 又是美好一天 2016-09-07 21:03发表 [回复]
    “scale(float sx , float sy, float px,float py):先将画布平移px,py,然后scale,scale结束之后再将画布平移回原基准点;我们再在之前的基础上绘制一个同样的矩形,x , y 均缩放为 0.5 倍,缩放中心为矩形的中心;”这句话的意思是缩放中心为矩形中心,那为什么canvas.scale(0.5f, 0.5f);画布在x,y方向上均缩放为 0.5 倍,使用默认基准点(原点 0,0),而不是矩形中心呢?
    9楼 code_river 2016-07-07 15:15发表 [回复]
    感谢楼主分享
    8楼 nailsoul 2016-03-29 23:30发表 [回复]
    赞一个 通过该栗子搞懂了save和restore的意思了 谢谢楼主了
    唉名字取的有奇异啊 xxState因该好理解点
    7楼 FionaRyan 2016-03-25 17:11发表 [回复]
    # 2016一线互联网名企集结号# #实习#
    “简寻”联合阿里、搜狗、百度音乐、小米、蘑菇街、凤凰网等 20+ 一线互联网公司举办2016技术实习专场活动, 200+技术实习岗,通过审核的可获5-10 次名企面试推荐。
    【报名方式】
    1.活动报名地址:https://h5.jianxun.io开启一键求职
    2.搜索微信公众号:jianxun-fuwu
    【面向群体】
    1. 实习生:2017 年及以后毕业的本科生/研究生,寻找技术类的 春季 / 暑期 实习
    2. 应届生:2016 年应届毕业生 , 技术类工作
    【活动时间】
    3月15日 - 5月15日
    注:春季/暑期实习皆可,具体事宜可与企业单独商议
    【简寻提供】
    1.  直接面试机会
    一线企业:阿里B2B、搜狗、蘑菇街、小米、豌豆荚 等
    实力大牌:果壳网、人人、百度作业帮、凤凰网、么么嗖 等
    新锐团队:长亭科技、酷家乐、云校、野糖、海智网聚 等
    2. 活动福利
    最优秀的三名学生将获活动协办方—— 包子IT 免费提供得北美顶尖 IT公司(Microsoft、google、facebook、Linkedin、amazon等)工程师进行一对一的全英模拟面试+指导,面试优异者可直接获得北美名企的内推机会。
    其他优秀学生,包子IT 还将提供免费针对北美 CS 方向的模拟试题;顶尖公司和明星创业公司的多位老师们,将为大家讲解 题目+背后线索,认真阅读每一份考卷、每一行代码,帮助大家提高代码结构和质量。
    【活动流程】
    1.   报名注册 → 2.   简历审核 → 3.   电话面试 → 4. 企业推荐 → 5. 企业复面 → 6. 发放offer
    6楼 yubaoma 2016-03-16 09:51发表 [回复]
    mark
    5楼 xiaomingi07 2016-02-26 15:41发表 [回复]
    介绍的东西太实用了,而且栗子也容易理解,一定要踩一下!
    4楼 mr_gang1992 2016-01-21 16:54发表 [回复]
    楼主在上面写文字的时候,遇到一个问题,就是文字跟着画布旋转了,如何才能保证文字,文字不旋转...望楼主解答啊。。。
    Re: 学问积年而成 2016-01-21 17:37发表 [回复]
    回复mr_gang1992:使用
    canvas.save();
    XXXX;
    canvas.restore();
    3楼 gongyong2008 2015-06-03 13:16发表 [回复]
    大神带我飞啊
    2楼 kangGe_studio 2015-05-10 16:34发表 [回复]
    赞一个
    1楼 LFy_Yu 2015-05-08 09:10发表 [回复]
    给力
    * 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    <script type="text/javascript">var fileName = '45234419'; var commentscount = 11; var islock = false</script><script type="text/javascript" src="http://static.blog.csdn.net/scripts/comment.js"></script>
    <script type="text/javascript">$(function () { $("#ad_frm_0").height("90px"); setTimeout(function(){ $("#ad_frm_2").height("200px"); },1000); });</script> <script language="javascript" type="text/javascript">$(function(){ setTimeout(function(){ $.get("/tianjian4592/svc/GetTagContent",function(data){ $(".tag_list").html(data).show(); }); }); },500);</script> <script type="text/javascript">$(function(){ setTimeout(function(){ $(".comment_body:contains('回复')").each(function(index,item){ var u=$(this).text().split(':')[0].toString().replace("回复","") var thisComment=$(this); if(u) { $.getJSON("https://passport.csdn.net/get/nick?callback=?", {users: u}, function(a) { if(a!=null&&a.data!=null&&a.data.length>0) { nick=a.data[0].n; if(u!=nick) { thisComment.text(thisComment.text().replace(u,nick)); } } }); } }); },200); setTimeout(function(){ $(".math").each(function(index,value){$(this).find("span").last().css("color","#fff"); }) },5000); setTimeout(function(){ $(".math").each(function(index,value){$(this).find("span").last().css("color","#fff"); }) },10000); setTimeout(function(){ $(".math").each(function(index,value){$(this).find("span").last().css("color","#fff"); }) },15000); setTimeout(function(){ $("a img[src='http://js.tongji.linezing.com/stats.gif']").parent().css({"position":"absolute","left":"50%"}); },300); }); function loginbox(){ var $logpop=$("#pop_win"); $logpop.html(' src="https://passport.csdn.net/account/loginbox?service=http://static.blog.csdn.net/callback.htm" frameborder="0" height="600" width="400" scrolling="no">'); $('#popup_mask').css({ opacity: 0.5, width: $( document ).width() + 'px', height: $( document ).height() + 'px' }); $('#popup_mask').css("display","block"); $logpop.css( { top: ($( window ).height() - $logpop.height())/ 2 + $( window ).scrollTop() + 'px', left:($( window ).width() - $logpop.width())/ 2 } ); setTimeout( function () { $logpop.show(); $logpop.css( { opacity: 1 } ); }, 200 ); $('#popup_mask').unbind("click"); $('#popup_mask').bind("click", function(){ $('#popup_mask').hide(); var $clopop = $("#pop_win"); $("#common_ask_div_sc").css("display","none"); $clopop.css( { opacity: 0 } ); setTimeout( function () { $clopop.hide(); }, 350 ); return false; }); }</script> <script language="javascript" type="text/javascript" src="http://ads.csdn.net/js/async_new.js"></script>
    • 个人资料
      • 访问:242632次
      • 积分:2709
      • 等级:
      • 排名:第10054名
      • 原创:56篇
      • 转载:0篇
      • 译文:0篇
      • 评论:340条
    <script type="text/javascript">$(function () { $("#btnSubmit").click(function () { search(); }); $("#frmSearch").submit(function () { search(); return false; }); function search() { var url = "http://so.csdn.net/so/search/s.do?q=" + encodeURIComponent($("#inputSearch").val()) + "&u=" + username + "&t=blog"; window.location.href = url; } });</script>
    • 最新评论
    id="iframeu2734128_0" src="http://pos.baidu.com/acrm?sz=200x200&rdid=2734128&dc=2&di=u2734128&dri=0&dis=0&dai=2&ps=2908x314&coa=at%3D3%26rsi0%3D200%26rsi1%3D200%26pat%3D6%26tn%3DbaiduCustNativeAD%26rss1%3D%2523FFFFFF%26conBW%3D1%26adp%3D1%26ptt%3D0%26titFF%3D%2525E5%2525BE%2525AE%2525E8%2525BD%2525AF%2525E9%25259B%252585%2525E9%2525BB%252591%26titFS%3D%26rss2%3D%2523000000%26titSU%3D0%26ptbg%3D90%26piw%3D0%26pih%3D0%26ptp%3D0&dcb=BAIDU_SSP_define&dtm=HTML_POST&dvi=0.0&dci=-1&dpt=none&tsr=0&tpr=1478326006940&ti=Canvas%E4%B9%8Btranslate%E3%80%81scale%E3%80%81rotate%E3%80%81skew%E6%96%B9%E6%B3%95%E8%AE%B2%E8%A7%A3%EF%BC%81%20-%20Ajian_studio%20-%20%E5%8D%9A%E5%AE%A2%E9%A2%91&ari=2&dbv=2&drs=3&pcs=1829x1019&pss=1829x19885&cfv=0&cpl=4&chi=3&cce=true&cec=UTF-8&tlm=1478326006&rw=1019&ltu=http%3A%2F%2Fblog.csdn.net%2Ftianjian4592%2Farticle%2Fdetails%2F45234419&ecd=1&psr=1920x1080&par=1846x1080&pis=-1x-1&ccd=24&cja=false&cmi=6&col=zh-CN&cdo=-1&tcn=1478326007&qn=c31dff3e505510bd&tt=1478326006861.137.138.470" width="200" height="200" align="center,center" vspace="0" hspace="0" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" style="border:0; vertical-align:bottom;margin:0;" allowtransparency="true">
    <script type="text/javascript">(window['cproStyleApi'] = window['cproStyleApi'] ||{})['u2734128']={at:'3',rsi0:'200',rsi1:'200',pat:'6',tn:'baiduCustNativeAD',rss1:'#FFFFFF',conBW:'1',adp:'1',ptt:'0',titFF:'%E5%BE%AE%E8%BD%AF%E9%9B%85%E9%BB%91',titFS:'',rss2:'#000000',titSU:'0',ptbg:'90',piw:'0',pih:'0',ptp:'0'};/*服务器频道首页置顶Banner960*90,创建于2014-7-3*/(window.cproArray = window.cproArray || []).push({id:'u2734128'});</script> <script src="http://cpro.baidustatic.com/cpro/ui/c.js" type="text/javascript"></script>
    <script type="text/javascript" src="http://c.csdnimg.cn/rabbit/cnick/cnick.js"></script><script type="text/javascript" src="http://static.blog.csdn.net/scripts/newblog.min.js"></script><script type="text/javascript" src="http://medal.blog.csdn.net/showblogmedal.ashx?blogid=1298835"></script><script type="text/javascript" src="http://static.blog.csdn.net/scripts/JavaScript1.js"></script> rel="stylesheet" type="text/css" href="//csdnimg.cn/pubfooter/css/pub_footer_2014.css" />
    <script id="noticeScript" type="text/javascript" btnid="header_notice_num" wrapid="note1" count="5" subcount="5" src="//csdnimg.cn/rabbit/notev2/js/notify.js?9d86d94"></script> <script type="text/javascript" src="http://passport.csdn.net/content/loginbox/login.js"></script><script type="text/javascript">document.write(" ");</script> <script type="text/javascript" src="http://www.csdn.net/ui/scripts/Csdn/counter.js"></script><script type="text/javascript" charset="UTF-8" src="http://message.csdn.net/msg.popup.js"></script><script type="text/javascript" src="http://ad.csdn.net/scripts/ad-blog.js"></script><script type="text/javascript">$(function () { function __get_code_toolbar(snippet_id) { return $(" 在CODE上查看代码片" + " 派生到我的代码片"); } $("[code_snippet_id]").each(function () { __s_id = $(this).attr("code_snippet_id"); if (__s_id != null && __s_id != "" && __s_id != 0 && parseInt(__s_id) > 70020) { __code_tool = __get_code_toolbar(__s_id); $(this).prev().find(".tools").append(__code_tool); } }); $(".bar").show(); });</script> <script id="csdn-toolbar-id" btnid="header_notice_num" wrapid="note1" count="5" subcount="5" type="text/javascript" src="http://c.csdnimg.cn/public/common/toolbar/js/toolbar.js"></script> href="http://c.csdnimg.cn/comm_ask/css/ask_float_block.css" type="text/css" rel="stylesheet" /> <script language="JavaScript" type="text/javascript" src="http://c.csdnimg.cn/comm_ask/js/libs/wmd.js"></script> <script language="JavaScript" type="text/javascript" src="http://c.csdnimg.cn/comm_ask/js/libs/showdown.js"></script> <script language="JavaScript" type="text/javascript" src="http://c.csdnimg.cn/comm_ask/js/libs/prettify.js"></script> <script language="JavaScript" type="text/javascript" src="http://c.csdnimg.cn/comm_ask/js/apps/ask_float_block.js"></script> rel="stylesheet" href="http://static.blog.csdn.net/css/blog_code.css" /> <script type="text/javascript" src="http://static.blog.csdn.net/scripts/saveToCode.js"></script> <script type="text/javascript" src="//csdnimg.cn/rabbit/tracking-ad/main.js?75eacd8"></script>
    rel="stylesheet" type="text/css" media="screen" href="http://ask.csdn.net/assets/ask_float_fonts_css-6b30a53970eb5c3a2a045e3df585b475.css" />

    提问

    您的问题将会被发布在“技术问答”频道 ×
    该问题已存在,请勿重复提问
    src="http://ask.csdn.net/upload.html">
    插入图片
    | | | | | |
      
    0 0 0:0
    推荐标签:
    我要悬赏
    取消 发布
    可能存在类似的问题:

    保存代码片

    整理和分享保存的代码片,请访问代码笔记
    • *标题
    • *描述
    •  标签
      Canvasx android画布x canvas.translatex canvas.scalex canvas.rotatex
    • 1
      点赞
    • 1
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值