1.需求:大部分的头像选择更换时都采取这样的功能,半透明的蒙版,中间一个正方形的选取框,底图随意移动缩放,确定后截取为背景头像。网易云,微信都是如此
2.实现:
1:代码:
package sss
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import com.example.sosrcpro.R
class CyclerView:View{
//图片来源
private var bitmap:Bitmap? = null
private var paint:Paint? = null
private var bitmapShader:BitmapShader? = null
private var bitmapPaint:Paint? = null
private var bitmatLeft = 0
private var bitmapTop = 0
private var roundPaint:Paint? =null
private var viewWidthHalf = 0F
private var viewHeihtHalf = 0F
var starX = 0F
var starY = 0F
var roundRadiu = 0F
var detector:ScaleGestureDetector? = null
var detectorListener:ScaleGestureDetector.OnScaleGestureListener? = null
var fixedRadiu = 0F
constructor(context:Context):this(context,null){
}
constructor(context: Context,attributeSet: AttributeSet?):this(context,attributeSet,0){
}
constructor(context: Context,attributeSet: AttributeSet?,style:Int):super(context,attributeSet,style){
paint = Paint()
bitmapPaint = Paint()
roundPaint = Paint()
paint?.apply {
setAntiAlias(true)
}
bitmap = BitmapFactory.decodeResource(resources, R.mipmap.timg)
detectorListener = object:ScaleGestureDetector.OnScaleGestureListener{
//返回true才能进入onScale
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector?) {
}
override fun onScale(detector: ScaleGestureDetector): Boolean {
var currentS = detector.currentSpan
var currentX = detector.currentSpanX
var currentY = detector.currentSpanY
var currentScale = detector.scaleFactor
Log.d("save","currentS:"+currentS.toString())
Log.d("save","currentScale:"+currentScale.toString())
if(currentScale>1)
scaleBitmapByTouch(1.1F)
else if(currentScale<1){
scaleBitmapByTouch(0.9F)
}
return true
}
}
detector = ScaleGestureDetector(getContext(),detectorListener)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
viewWidthHalf = measuredWidth/2F
viewHeihtHalf = measuredHeight/2F
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
var srcrect = Rect(0, 0, bitmap!!.getWidth(), bitmap!!.getHeight())
//单指移动图片
var dscrect = Rect(bitmatLeft, bitmapTop, bitmap!!.getWidth()+bitmatLeft, bitmap!!.getHeight()+bitmapTop)
//绘制底图
canvas?.drawBitmap(bitmap!!, srcrect, dscrect, bitmapPaint)
paint?.setColor(Color.parseColor("#80666666"))
var radiu = canvas?.width!! /3F
fixedRadiu = radiu/2F
var layerId = canvas.saveLayer(0F,0F,canvas.width*1F,canvas.height*1F,null,Canvas.ALL_SAVE_FLAG)
//绘制灰色半透明的蒙版
canvas?.drawRect(0F,0F,canvas.width*1F,canvas.height*1F,paint!!)
//DST_OUT:取下层绘制非交集部分。不显示上层(由于圆圈部分存在交集,不显示这下层部分,就镂空了,
// 进而显示底图,而底图其他部分显示其他非交集下层部分)
paint?.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_OUT))
paint?.setColor(Color.BLUE)
//画圆,通过透底得到镂空的底图
canvas?.drawCircle(canvas.width/2F,canvas.height/2F,fixedRadiu,paint!!)
paint?.setXfermode(null)
canvas?.restoreToCount(layerId)
}
fun setBitmap(bitmap:Bitmap){
this.bitmap = bitmap
invalidate()
}
fun getBitmapFormView():Bitmap{
return bitmap!!
}
fun scaleBitmapByTouch(scale:Float){
var matrix = Matrix()
matrix.setScale(scale,scale)
bitmap = Bitmap.createBitmap(bitmap!!,0,0,bitmap!!.width,bitmap!!.height,matrix,true)
}
fun getCircleBitmap( bitmap:Bitmap) {
var circleBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
var canvas = Canvas(circleBitmap)
var paint = Paint()
var rect = Rect(0, 0, bitmap.getWidth(), bitmap.getHeight())
var rectF = RectF(rect);
var roundRa = 0.0f
if (bitmap.getWidth() > bitmap.getHeight()) {
roundRa = bitmap.getHeight() / 2.0f
} else {
roundRa = bitmap.getWidth() / 2.0f
}
}
//是频繁调用的,starX,starY的初始化要放在类中,不是在函数中,否则不管是任何触摸类型都设置为0了。
//加上放大缩小,范围控制,截图,保存。
override fun onTouchEvent(event: MotionEvent?): Boolean {
detector!!.onTouchEvent(event)
var touchType = event!!.actionMasked
when(touchType){
//单指,多指
MotionEvent.ACTION_DOWN->{
starX =event.x
starY = event.y
Log.e("native","ACTION_DOWN")
}
//单指,多指
MotionEvent.ACTION_MOVE->{
var x =event.x
var y = event.y
var xxx = x-starX
var yyy =y-starY
starX =x
starY = y
setBitmapPosition(bitmatLeft+xxx,bitmapTop+yyy)
Log.e("native","ACTION_MOVE")
var pointCount = event.pointerCount
if(pointCount==2){
}
}
//单指,多指
MotionEvent.ACTION_UP->{
Log.e("native","ACTION_UP")
}
//单指多指都是ACTION_MOVE
//多指
MotionEvent.ACTION_POINTER_DOWN->{
Log.e("native","ACTION_POINTER_DOWN")
}
//多指
MotionEvent.ACTION_POINTER_UP->{
Log.e("native","ACTION_POINTER_UP")
}
}
return true
}
fun setBitmapPosition(left:Float,top:Float){
bitmatLeft = left.toInt()
bitmapTop = top.toInt()
invalidate()
}
fun getRoundBitmap(radiu:Float):Bitmap{
//画布设置为边长为圆框的半径
var output = Bitmap.createBitmap(fixedRadiu.toInt()*2,fixedRadiu.toInt()*2,Bitmap.Config.ARGB_8888)
var canvas = Canvas(output)
//原图
var srcRect = Rect(0,0,bitmap!!.width,bitmap!!.height)
//移动,放置的位置又这个确定,看到的圆框与新的左上角的圆要效果一样。相当于多移动一部分(即两个圆的距离)
var dscRect = Rect(bitmatLeft-(viewWidthHalf.toInt()-fixedRadiu.toInt()),bitmapTop-(viewHeihtHalf.toInt()-fixedRadiu.toInt()),bitmap!!.width+bitmatLeft-(viewWidthHalf.toInt()-fixedRadiu.toInt()),bitmap!!.height+bitmapTop-(viewHeihtHalf.toInt()-fixedRadiu.toInt()))
//PorterDuff.Mode.SRC_IN 后不管背景颜色如何,我这都是灰色的。
canvas.drawARGB(0,255,0,0)
roundPaint!!.color = Color.parseColor("#ff424242")
//圆的中心坐标不变,移动图片进行绘制即可。
canvas.drawCircle(fixedRadiu,fixedRadiu,fixedRadiu,roundPaint!!)
roundPaint!!.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC_IN))
canvas.drawBitmap(bitmap!!,srcRect,dscRect,roundPaint)
roundPaint!!.setXfermode(null) //每次绘制完毕后都应重置为null
//二次截取
return output
}
fun saveBitmap():Bitmap{
return getRoundBitmap(fixedRadiu)
}
}
2效果:
这里的缩放还没有限制,单指移动也还没限制,懒得弄了,这只是练练手,懂个思路就行,其实大概也能用了,哈哈哈哈哈哈哈哈哈。
代码改了,效果图没改,代码改后截图大小刚好和圆框大小一致。原来的是和原图大小一样,这样就没办法直接用了。