Android 仿照QQ剪裁头像(完结篇)

转载请注明出处:
http://blog.csdn.net/u014163726?viewmode=contents
本文出自Wrh的博客

闲扯

最近刚好空闲下来又因为以前有人问到过这个仿qq上传头像的问题,抓紧时间撸出一篇博客

  1. 本章内容部分手势检测出自翔神的博客
  2. 本章内容匆忙赶出估计会有bug希望发现的人可以在评论告诉我
  3. 好好学习,天天向上,像大神学习

正文

还记得我上一篇关于仿qq上传头像时继承的是View,现在我们继承ImageView来实现,继承ImageView的好处有很多包括在缩放的处理上等等。

首先我们还是先从在图片上绘制阴影开始。

private RectF shelterR;
private Paint mPaint;
public CustomImageView(Context context) {
    this(context,null);
}

public CustomImageView(Context context, AttributeSet attrs) {
    this(context, attrs,0);
}

public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init() {
    mPaint = new Paint();
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setStrokeWidth(2);
    mPaint.setAntiAlias(true);
    mPaint.setColor(getResources().getColor(R.color.background));
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (getDrawable() == null){
        return;
    }
    float width = getDrawable().getIntrinsicWidth();
    float height = getDrawable().getIntrinsicHeight();
    if (shelterR == null || shelterR.isEmpty()) {
        shelterR = new RectF(0, 0,width, height);
    }
    // 画入前景圆形蒙板层
    int sc = canvas.saveLayer(shelterR, null, Canvas.MATRIX_SAVE_FLAG
            | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
            | Canvas.FULL_COLOR_LAYER_SAVE_FLAG
            | Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.ALL_SAVE_FLAG);
    canvas.drawRect(shelterR, mPaint);
    canvas.restoreToCount(sc);
}

可能很多朋友要问这段代码的意思了

    int sc = canvas.saveLayer(shelterR, null, Canvas.MATRIX_SAVE_FLAG
            | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
            | Canvas.FULL_COLOR_LAYER_SAVE_FLAG
            | Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.ALL_SAVE_FLAG);
    canvas.drawRect(shelterR, mPaint);
    canvas.restoreToCount(sc);

先放一个参考资料Android中的canvas介绍
我的理解可能比较简单就认为这其实就像拓印纸

canvas.saveLayer

就像是在一层新的画布上绘制东西

canvas.restoreToCount(sc)

则是把在新画布绘制的所有东西拓印到上一层

我们在新画布上的任何操作绘制出来的图形都会restore到上一层画布,阴影绘制完毕我们就开始绘制中心的圆,这个我已经讲过挺多次的了这次就不详细说明了Android PorterDuff.Mode与Canvas实际使用

这些前期准备都做完之后我们考虑一些细节,我们应该让我们的图片居中显示并且保证图片的长宽肯定要大于剪裁圆形的直径

  @Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    getViewTreeObserver().addOnGlobalLayoutListener(this);
}

@SuppressWarnings("deprecation")
@Override
protected void onDetachedFromWindow()
{
    super.onDetachedFromWindow();
    getViewTreeObserver().removeGlobalOnLayoutListener(this);
}

    @Override
public void onGlobalLayout() {
    if (once)
    {
        Drawable d = getDrawable();
        if (d == null)
            return;
        int width = getWidth();
        int height = getHeight();
        int diameter = mRadius * 2;
        int dw = d.getIntrinsicWidth();
        int dh = d.getIntrinsicHeight();
        float scale = 1.0f;
        if (dw < diameter && dh > diameter)
        {
            scale = diameter * 1.0f / dw;
        }
        if (dh < diameter && dw > diameter){
            scale = diameter * 1.0f / dh;
        }
        if (dh < diameter && dw <diameter){
            scale = Math.max(diameter * 1.0f / dw, diameter * 1.0f / dh);
        }
        initScale = scale;
        // 图片移动至屏幕中心
        mScaleMatrix.postTranslate((width - dw) / 2, (height - dh) / 2);
        mScaleMatrix
                .postScale(scale, scale, getWidth() / 2, getHeight() / 2);
        setImageMatrix(mScaleMatrix);
        once = false;
    }
}

我们再onGlobalLayout()中保证了图片的宽高,ok到这一步我们已经初见模型了

移动图片

接下来我们要考虑的就是移动这个图片,但要注意我们要限制它移动的距离。

 @Override
public boolean onTouchEvent(MotionEvent event) {
    if (mGestureDetector.onTouchEvent(event))
        return true;
    mScaleGestureDetector.onTouchEvent(event);
    float x = 0, y = 0;
    // 拿到触摸点的个数
    final int pointerCount = event.getPointerCount();
    // 得到多个触摸点的x与y均值
    for (int i = 0; i < pointerCount; i++)
    {
        x += event.getX(i);
        y += event.getY(i);
    }
    x = x / pointerCount;
    y = y / pointerCount;
    /**
     * 每当触摸点发生变化时,重置mLasX , mLastY
     */
    if (pointerCount != lastPointerCount)
    {
        mLastX = x;
        mLastY = y;
    }
    lastPointerCount = pointerCount;
    switch (event.getAction()){
        case MotionEvent.ACTION_MOVE:
            float dx = x - mLastX;
            float dy = y - mLastY;

            if (shelterR.left + dx >= circleR.left || shelterR.right + dx <= circleR.right){
                dx = 0;
            }
            if (shelterR.top + dy >  circleR.top || shelterR.bottom + dy <= circleR.bottom){
                dy = 0;
            }
            mScaleMatrix.postTranslate(dx, dy);
            setImageMatrix(mScaleMatrix);
            mLastX = x;
            mLastY = y;
            postInvalidate();
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            lastPointerCount = 0;
            break;
    }
    return true;
}

我们在Touch事件中做了判断以保证移动不会越界shelterR是我们图形的RectF,circleR是我们的中心圆形的RectF。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (getDrawable() == null){
        return;
    }
    getShelterRectF();
    // 画入前景圆形蒙板层
    int sc = canvas.saveLayer(shelterR, null, Canvas.MATRIX_SAVE_FLAG
            | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
            | Canvas.FULL_COLOR_LAYER_SAVE_FLAG
            | Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.ALL_SAVE_FLAG);
    canvas.drawRect(shelterR, mPaint);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
    mPaint.setColor(Color.WHITE);
    canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, mPaint);
    if (circleR == null){
        circleR = new RectF(getWidth() / 2 - mRadius, getHeight() / 2 - mRadius,getWidth() / 2 + mRadius,getHeight() / 2 + mRadius);
    }
    canvas.restoreToCount(sc);
    mPaint.setXfermode(null);
    mPaint.setColor(getResources().getColor(R.color.background));
}   
 /**
 * 用来保证阴影的大小足以遮住图片
 * @return
 */
private RectF getShelterRectF(){
    float x = (int) getTranslateX();
    float y = (int) getTranslateY();
    float width = getDrawable().getIntrinsicWidth() * getScale();
    float height = getDrawable().getIntrinsicHeight() * getScale();
    if (shelterR == null) {
        shelterR = new RectF(x, y,width + x, height + y);
    }else{
        shelterR.set(x, y,width + x, height + y);
    }
    return shelterR;
}

可以看到我们在onDraw()中做的事情并不多,这也是继承ImageView的好处之一,ok现在就让我享受移动的感觉(移动的信号真的差),效果如图

缩放图片

现在我们基本成型了,我们继续完成缩放功能,在这还是要说明本章手势检测功能出自Android 手势检测实战 打造支持缩放平移的图片预览效果

  @Override
public boolean onScale(ScaleGestureDetector detector) {
    float scale = getScale();
    float scaleFactor = detector.getScaleFactor();

    if (getDrawable() == null)
        return true;

    /**
     * 缩放的范围控制
     */
    if ((scale < SCALE_MAX && scaleFactor > 1.0f)
            || (scale > initScale && scaleFactor < 1.0f))
    {
        /**
         * 最大值最小值判断
         */
        if (scaleFactor * scale < initScale)
        {
            scaleFactor = initScale / scale;
        }
        if (scaleFactor * scale > SCALE_MAX)
        {
            scaleFactor = SCALE_MAX / scale;
        }
        /**
         * 设置缩放比例
         */
        mScaleMatrix.postScale(scaleFactor, scaleFactor,
                detector.getFocusX(), detector.getFocusX());
        checkBorderAndCenterWhenScale();
        setImageMatrix(mScaleMatrix);
    }
    return true;
}

@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
    return true;
}

@Override
public void onScaleEnd(ScaleGestureDetector detector) {

}
/**
 * 在缩放时,进行图片显示范围的控制
 */
private void checkBorderAndCenterWhenScale()
{

    RectF rect = getMatrixRectF();
    float deltaX = 0;
    float deltaY = 0;

    int width = getWidth();
    int height = getHeight();

    // 如果宽或高大于屏幕,则控制范围
    if (rect.width() >= width)
    {
        if (rect.left > 0)
        {
            deltaX = -rect.left;
        }
        if (rect.right < width)
        {
            deltaX = width - rect.right;
        }
    }
    if (rect.height() >= height)
    {
        if (rect.top > 0)
        {
            deltaY = -rect.top;
        }
        if (rect.bottom < height)
        {
            deltaY = height - rect.bottom;
        }
    }
    // 如果宽或高小于屏幕,则让其居中
    if (rect.width() < width)
    {
        deltaX = width * 0.5f - rect.right + 0.5f * rect.width();
    }
    if (rect.height() < height)
    {
        deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
    }
    mScaleMatrix.postTranslate(deltaX, deltaY);
}

还是要像大神学习(づ ̄ 3 ̄)づ,再加上双击判断

 private class AutoScaleRunnable implements Runnable
{
    static final float BIGGER = 1.07f;
    static final float SMALLER = 0.93f;
    private float mTargetScale;
    private float tmpScale;

    /**
     * 缩放的中心
     */
    private float x;
    private float y;

    /**
     * 传入目标缩放值,根据目标值与当前值,判断应该放大还是缩小
     *
     * @param targetScale
     */
    public AutoScaleRunnable(float targetScale, float x, float y)
    {
        this.mTargetScale = targetScale;
        this.x = x;
        this.y = y;
        if (getScale() < mTargetScale)
        {
            tmpScale = BIGGER;
        } else
        {
            tmpScale = SMALLER;
        }

    }

    @Override
    public void run()
    {
        // 进行缩放
        mScaleMatrix.postScale(tmpScale, tmpScale, x, y);
        checkBorderAndCenterWhenScale();
        setImageMatrix(mScaleMatrix);

        final float currentScale = getScale();
        // 如果值在合法范围内,继续缩放
        if (((tmpScale > 1f) && (currentScale < mTargetScale))
                || ((tmpScale < 1f) && (mTargetScale < currentScale)))
        {
            WrhImageView.this.postDelayed(this, 16);
        } else
        // 设置为目标的缩放比例
        {
            final float deltaScale = mTargetScale / currentScale;
            mScaleMatrix.postScale(deltaScale, deltaScale, x, y);
            checkBorderAndCenterWhenScale();
            setImageMatrix(mScaleMatrix);
            isAutoScale = false;
        }

    }
}

剪裁图片

最后一步了,想必大家的心情都很激动。

 /**
 * 剪裁头像
 * @return
 */
public Bitmap clipBitmap(){
    final Paint paint = new Paint();
    paint.setAntiAlias(true);
    int dw = getDrawable().getIntrinsicWidth();
    int dh = getDrawable().getIntrinsicHeight();
    float x = getTranslateX() - (getWidth() - dw) / 2;
    float y = getTranslateY() - (getHeight() - dh) / 2;
    mBitmap = zoomBitmap(mBitmap);
    Bitmap target = Bitmap.createBitmap(mRadius * 2, mRadius * 2, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(target);
    canvas.drawCircle(mRadius, mRadius, mRadius, paint);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

    canvas.drawBitmap(mBitmap,- (dw / 2 - mRadius) + x ,- (dh / 2 - mRadius) + y, paint);
    return target;
}

我们来实际测试一下感受一下

结束语

啊,啊,啊,代码基于Android Studio所写,下一篇又不知道要猴年马月了,和大神还是有差距要更努力↖(^ω^)↗
Bye

项目代码

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值