自己动手造轮子系列———实现『转转 APP』Indicator 效果

2,432 阅读3分钟

一.『转转APP』效果预览

注意看Indicator指示器

二.动作分析

  1. 左边部分

左边部分是一个白色的圆圈,圆圈中用红色字体显示当前页数,在ViewPager滑动时,白色圆圈绕Y轴旋转,在旋转180度时,即显示背面的时候,圆圈中字变成下一页的页码。

  1. 右边部分

右边部分比较简单,一个带缺口的椭圆,上面显示着总页数。

三.各个击破难点

1. 用canvas绘制绕Y轴旋转

Canvas 绘制图片时,如果是在绕Z轴旋转是比较简单,使用Matrix矩阵:

Matrix matrix = new Matrix();
matrix.setRotate(angle);
Bimtap newBitmap = Bitmap.createBitmap(oldBitmap,0,0,oldBitmap.getWidth(),
    oldBitmap.getHeight(),matrix,true);

但是这只能在绘制图片时进行旋转,如果要对一个整体旋转则无法做到,比如,上面转转的指示器坐标的圆圈,圆圈是一个圆,上面还有数字,要实现圆和数字一起旋转,所以无法通过上述方法完成。

而且这个问题我在谷歌上搜索,也不太好表述,刚开始搜索「android canvas 3D旋转」,结果也不太相符,后来搜索「android canvas绕y轴 旋转」,才找到符合的。

使用 Android.graphics.Camera 的 rotateY 接口实现绕 Y 轴旋转时矩阵的运算。

首先声明camera

camera = new Camera();

在Ondraw方法中,首先对canvas状态进行保存,使用的是canvas.save(),然后将camera状态保存,将camera旋转一个角度rotateAngle,在获取camera的矩阵赋值到matrix中,最后将camera状态恢复至旋转之前的状态。然后将matrix追加到当前canvas的矩阵中,使用的是concat方法,这时候整个画布相当于旋转了rotateAngle角度,画完圆之后再恢复至原来的状态。

canvas.save();
Matrix matrix = new Matrix(); 
camera.save();
camera.rotateY(rotateAngle);
camera.getMatrix(matrix);
camera.restore();

mPaint.setColor(textBackgroundColor);
int centerX = diameter / 2;
int centerY = diameter / 2;
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
canvas.concat(matrix);

canvas.drawCircle(diameter / 2, diameter / 2, diameter / 2, mPaint);

canvas.restore();

2. canvas drawText文字垂直居中

这个问题,如果没有遇到还真不知道会出现这种问题,一般想象的,我设置画笔的gravity属性为Paint.Align.CENTER,在将文字的中点设置下不就ok了么,如下面的代码:

textPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("of " + maxPage, rect.centerX(), rect.centerY(), textPaint);

出现的效果,如图所示,发现文字是偏上:

这是怎么回事呢?其实我们设置的文字爱中心点处绘制,实际上,文字是以基准线为锚点进行绘制的,英文叫做baseline.

在维基百科中可以看到基线的解释:

所以我们如果想让文字居中,则需要将绘制点的Y坐标向下移动。

基线到字体顶端的距离为top,基线到字体底端距离为bottom,则有以下等式:

(top + baseline)+ (bottom + baseline) / 2 = centenY() 

那么经过计算就可以算出baseline的值,通过Paint.FontMetrics获取top和bottom值,代码如下:

//画文字
Rect rect = new Rect(diameter, 0, (int) (diameter * 3.4f), diameter);//右边的背景图片的Rect
Paint textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize((int) (diameter * 0.6f));
textPaint.setStyle(Paint.Style.FILL);
//该方法即为设置基线上那个点究竟是left,center,还是right  这里我设置为center
textPaint.setTextAlign(Paint.Align.CENTER);

Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
float top = fontMetrics.top;//为基线到字体上边框的距离,即上图中的top
float bottom = fontMetrics.bottom;//为基线到字体下边框的距离,即上图中的bottom
int baseLineY = (int) (rect.centerY() - top / 2 - bottom / 2);//基线中间点的y轴计算公式
canvas.drawText("of " + maxPage, rect.centerX(), baseLineY, textPaint);

3. 自动轮播

自动轮播,通过Handler+Timer即可。

//定时自动播放
timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        Message message = new Message();
        message.what = 1;
        if (mViewPager.getCurrentItem() == Integer.MAX_VALUE - 1) {
            currentIndex = -1;
        }
        currentIndex = mViewPager.getCurrentItem();
        message.arg1 = currentIndex + 1;
        mHandler.sendMessage(message);
    }
},1000,2000);

然后自定义Handler.

//定时轮播图片,需要在主线程里面修改 UI
private Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                mViewPager.setCurrentItem(msg.arg1,true);
        }
    }
};

四.代码实现

代码在Github上,请不要吝啬Star噢。

RotateIndicatorView

联系方式和建议

微博:orzangleli

所有原创文章版权归orzangleli所有,转载需声明:www.orzangleli.com/2016/11/10/…