Android Kotlin 위 챗 프로필 사진 재단 방법 예시

0.머리말
최근 에 갑자기 많은 일이 생 겨 서 또 강 자 와 투 표를 했 습 니 다.어 쩔 수 없 이 죄송합니다.최근 생활 에서 많은 깨 달 음 을 얻 었 습 니 다.한 남자 의 강 요 는 바로 일,공부 와 가정 을 균형 있 게 하 는 것 입 니 다.이 점 은 파악 하기 어렵 습 니 다.가정 이 화목 해 야 할 뿐만 아니 라 자신의 가치 의 실현 도 보장 하여 평범함 에 빠 지지 않도록 해 야 합 니 다.모든 사람의 상황 이 다 릅 니 다.어떤 경험 도 그대로 옮 길 수 없습니다.뭐라고 해 야 할 지 계속 모색 해 보 세 요.
1.분석
전체 효 과 는 위 챗 을 모방 하여 만 든 것 입 니 다.효 과 는 그림 과 같 습 니 다.

전체 효 과 는 갤러리 에서 그림 을 선택 하고 재단 하 는 것 입 니 다.갤러리 에서 선택 하 는 것 은 할 말 이 없습니다.재단 컨트롤 을 어떻게 하 는 지 말씀 해 보 세 요.이 재단 컨트롤 은 ClipImageView 입 니 다.그림자 가리개,투명 한 상자,그리고 그림 의 표시,그리고 그림 을 이동 할 수 있 습 니 다.
2.코드

class ClipImageView(context: Context, attributeSet: AttributeSet?) : ImageView(context, attributeSet)
{

  private val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)

  var clipWidth = 300
    set(value)
    {
      field = value
      if (isAttachedToWindow)
      {
        postInvalidate()
      }
    }

  var clipHeight = 300
    set(value)
    {
      field = value
      if (isAttachedToWindow)
      {
        postInvalidate()
      }
    }

  var minScale = 1.0f

  var maxScale = 1.0f

  private var rectColor = Color.BLACK

  private var lastTouchX = 0F

  private var lastTouchY = 0F

  private val transMatrix = Matrix()

  private var isTouching = false

  private var scale = 1.0f

  var onsaveClipImageListener: OnSaveClipImageListsner? = null

  private val scaleGestureDetectorListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener()
  {

    override fun onScale(detector: ScaleGestureDetector?): Boolean
    {
      val curScaleFactor = detector?.scaleFactor ?: 1.0f
      var curScale = scale * curScaleFactor
      curScale = if (curScale >= 1.0f) Math.min(maxScale, curScale) else Math.max(minScale, curScale)
      val scaleFactor = if (curScale > scale) 1 + (curScale - scale) / scale else 1.0f - (scale - curScale) / scale
      transMatrix.postScale(scaleFactor, scaleFactor, detector?.focusX
          ?: 0f, detector?.focusY ?: 0f)
      postInvalidate()
      scale = curScale
      return true
    }

    override fun onScaleEnd(detector: ScaleGestureDetector?)
    {
      super.onScaleEnd(detector)
    }
  }

  private var scaleGestureDetector: ScaleGestureDetector

  constructor(context: Context) : this(context, null)

  init
  {
    paint.strokeJoin = Paint.Join.ROUND
    scaleGestureDetector = ScaleGestureDetector(context, scaleGestureDetectorListener)
    if (attributeSet != null)
    {
      pareseAttributeSet(attributeSet)
    }
    setBackgroundColor(Color.WHITE)
  }

  private fun pareseAttributeSet(attributeSet: AttributeSet)
  {
    val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ClipImageView)
    clipWidth = typedArray.getDimensionPixelOffset(R.styleable.ClipImageView_clip_width, clipWidth)
    clipHeight = typedArray.getDimensionPixelOffset(R.styleable.ClipImageView_clip_width, clipHeight)
    rectColor = typedArray.getColor(R.styleable.ClipImageView_rect_color, rectColor)
    minScale = typedArray.getFloat(R.styleable.ClipImageView_min_scale, minScale)
    maxScale = typedArray.getFloat(R.styleable.ClipImageView_max_scale, maxScale)
    typedArray.recycle()
  }

  override fun layout(l: Int, t: Int, r: Int, b: Int)
  {
    super.layout(l, t, r, b)
    if (clipWidth > measuredWidth)
    {
      clipWidth = measuredWidth
    }
    if (clipHeight > measuredHeight)
    {
      clipHeight = measuredHeight
    }

  }


  override fun onTouchEvent(event: MotionEvent?): Boolean
  {
    if (event?.pointerCount ?: 1 >= 2)
    {
      isTouching = false
      return scaleGestureDetector.onTouchEvent(event)
    }
    else
    {
      when (event?.action)
      {
        MotionEvent.ACTION_DOWN ->
        {
          isTouching = true
          lastTouchX = event.x
          lastTouchY = event.y
        }

        MotionEvent.ACTION_MOVE ->
        {
          if (isTouching && event.pointerCount == 1)
          {
            val offsetX = event.x - lastTouchX
            val offsetY = event.y - lastTouchY
            transMatrix.postTranslate(offsetX, offsetY)
            lastTouchX = event.x
            lastTouchY = event.y
            postInvalidate()
          }
        }

        MotionEvent.ACTION_UP ->
        {
          isTouching = false
        }
      }
      return true
    }
  }

  override fun onDraw(canvas: Canvas?)
  {
    canvas?.let {
      val saveState = it.saveCount
      it.save()
      it.concat(transMatrix)
      super.onDraw(canvas)
      it.restoreToCount(saveState)
      drawMask(it)
      drawRect(it)

    }
  }

  private fun drawMask(canvas: Canvas)
  {
    paint.style = Paint.Style.FILL
    paint.color = Color.parseColor("#A0000000")
    canvas.drawRect(0.0f, 0.0f, width.toFloat(), (height / 2 - clipHeight / 2).toFloat(), paint)
    canvas.drawRect((width / 2 + clipWidth / 2).toFloat(), (height / 2 - clipHeight / 2).toFloat(), width.toFloat(), (height / 2 + clipHeight / 2).toFloat(), paint)
    canvas.drawRect(0.0f, (height / 2 + clipHeight / 2).toFloat(), width.toFloat(), height.toFloat(), paint)
    canvas.drawRect(0.0f, (height / 2 - clipHeight / 2).toFloat(), (width / 2 - clipWidth / 2).toFloat(), (height / 2 + clipHeight / 2).toFloat(), paint)
  }

  private fun drawRect(canvas: Canvas)
  {
    paint.style = Paint.Style.FILL_AND_STROKE
    paint.color = rectColor
    paint.strokeWidth = 4.0f
    val offset = paint.strokeWidth / 2
    val left: Float = (width / 2 - clipWidth / 2).toFloat() - offset
    val top: Float = (height / 2 - clipHeight / 2).toFloat() - offset
    val right: Float = (width / 2 + clipWidth / 2).toFloat() + offset
    val bottom: Float = (height / 2 + clipHeight / 2).toFloat() + offset
    canvas.drawLine(left, top, right, top, paint)
    canvas.drawLine(right, top, right, bottom, paint)
    canvas.drawLine(left, bottom, right, bottom, paint)
    canvas.drawLine(left, top, left, bottom, paint)
  }

  interface OnSaveClipImageListsner
  {
    fun onImageFinishedSav()
  }


  inner class SaveTask(private val filePath: String) : AsyncTask<Unit, Unit, Unit>()
  {

    override fun doInBackground(vararg params: Unit?): Unit
    {
      saveClipImage(filePath)

    }

    override fun onPostExecute(result: Unit?)
    {
      super.onPostExecute(result)
      onsaveClipImageListener?.onImageFinishedSav()
    }
  }


  fun clipAndSaveImage(filePath: String)
  {
    SaveTask(filePath).execute()
  }

  private fun saveClipImage(filePath: String)
  {
    val clipBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val clipCanvas = Canvas(clipBitmap)
    draw(clipCanvas)
    try
    {
      val outputStream = FileOutputStream(filePath)
      val bitmap = Bitmap.createBitmap(clipBitmap, width / 2 - clipWidth / 2, height / 2 - clipHeight / 2, clipWidth, clipHeight, transMatrix, true)
      bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
      outputStream.close()
    }
    catch (e: IOException)
    {
      e.printStackTrace()
    }

  }
}
이 코드 는 ImageView 에서 계승 한 것 임 을 알 수 있 습 니 다.
코드 세그먼트 먼저 보기

private fun pareseAttributeSet(attributeSet: AttributeSet)
  {
    val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ClipImageView)
    clipWidth = typedArray.getDimensionPixelOffset(R.styleable.ClipImageView_clip_width, clipWidth)
    clipHeight = typedArray.getDimensionPixelOffset(R.styleable.ClipImageView_clip_width, clipHeight)
    rectColor = typedArray.getColor(R.styleable.ClipImageView_rect_color, rectColor)
    minScale = typedArray.getFloat(R.styleable.ClipImageView_min_scale, minScale)
    maxScale = typedArray.getFloat(R.styleable.ClipImageView_max_scale, maxScale)
    typedArray.recycle()
  }
레이아웃 파일 의 속성 을 분석 합 니 다.클립 width 와 클립 height 는 각각 재단 상자 의 너비 와 높이 를 대표 합 니 다.minScale 과 max Scale 은 최소 와 최대 크기 입 니 다.

override fun layout(l: Int, t: Int, r: Int, b: Int)
  {
    super.layout(l, t, r, b)
    if (clipWidth > measuredWidth)
    {
      clipWidth = measuredWidth
    }
    if (clipHeight > measuredHeight)
    {
      clipHeight = measuredHeight
    }

  }
layot 방법 에 clipWidth 와 clipHeight 를 설정 하여 설정 값 이 컨트롤 크기 보다 크 지 않도록 합 니 다.
draw Mask 방법 과 draw Rect 방법 은 커버 층 과 재단 상 자 를 그 리 는 데 사용 되 는데 그 중에서 커버 층 은 네 개의 사각형 이 고 재단 상 자 는 사각형 의 외곽 틀 입 니 다.

private fun drawMask(canvas: Canvas)
  {
    paint.style = Paint.Style.FILL
    paint.color = Color.parseColor("#A0000000")
    canvas.drawRect(0.0f, 0.0f, width.toFloat(), (height / 2 - clipHeight / 2).toFloat(), paint)
    canvas.drawRect((width / 2 + clipWidth / 2).toFloat(), (height / 2 - clipHeight / 2).toFloat(), width.toFloat(), (height / 2 + clipHeight / 2).toFloat(), paint)
    canvas.drawRect(0.0f, (height / 2 + clipHeight / 2).toFloat(), width.toFloat(), height.toFloat(), paint)
    canvas.drawRect(0.0f, (height / 2 - clipHeight / 2).toFloat(), (width / 2 - clipWidth / 2).toFloat(), (height / 2 + clipHeight / 2).toFloat(), paint)
  }

  private fun drawRect(canvas: Canvas)
  {
    paint.style = Paint.Style.FILL_AND_STROKE
    paint.color = rectColor
    paint.strokeWidth = 4.0f
    val offset = paint.strokeWidth / 2
    val left: Float = (width / 2 - clipWidth / 2).toFloat() - offset
    val top: Float = (height / 2 - clipHeight / 2).toFloat() - offset
    val right: Float = (width / 2 + clipWidth / 2).toFloat() + offset
    val bottom: Float = (height / 2 + clipHeight / 2).toFloat() + offset
    canvas.drawLine(left, top, right, top, paint)
    canvas.drawLine(right, top, right, bottom, paint)
    canvas.drawLine(left, bottom, right, bottom, paint)
    canvas.drawLine(left, top, left, bottom, paint)
  }
이 어 그림 을 손가락 에 따라 이동 하고 크기 를 조정 하 는 방법 을 살 펴 보 겠 습 니 다.trans Matrix 는 Matrix 류 입 니 다.이 를 통 해 Canvas 에 적용 하여 크기 조정 과 이동 을 실현 합 니 다.

override fun onTouchEvent(event: MotionEvent?): Boolean
  {
    if (event?.pointerCount ?: 1 >= 2)
    {
      isTouching = false
      return scaleGestureDetector.onTouchEvent(event)
    }
    else
    {
      when (event?.action)
      {
        MotionEvent.ACTION_DOWN ->
        {
          isTouching = true
          lastTouchX = event.x
          lastTouchY = event.y
        }

        MotionEvent.ACTION_MOVE ->
        {
          if (isTouching && event.pointerCount == 1)
          {
            val offsetX = event.x - lastTouchX
            val offsetY = event.y - lastTouchY
            transMatrix.postTranslate(offsetX, offsetY)
            lastTouchX = event.x
            lastTouchY = event.y
            postInvalidate()
          }
        }

        MotionEvent.ACTION_UP ->
        {
          isTouching = false
        }
      }
      return true
    }
  }
두 손가락 이 만 졌 을 때 이동 이벤트 에 Scale Gesture Detector 가 크기 를 조정 하지 않 으 면 이동 합 니 다.
먼저 보기 이동:
이동 거 리 를 transMatrix 에 적용 하고 post Invaidate()를 호출 하여 다시 그립 니 다.
크기 조정 처리 다시 보기

private val scaleGestureDetectorListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener()
  {

    override fun onScale(detector: ScaleGestureDetector?): Boolean
    {
      val curScaleFactor = detector?.scaleFactor ?: 1.0f
      var curScale = scale * curScaleFactor
      curScale = if (curScale >= 1.0f) Math.min(maxScale, curScale) else Math.max(minScale, curScale)
      val scaleFactor = if (curScale > scale) 1 + (curScale - scale) / scale else 1.0f - (scale - curScale) / scale
      transMatrix.postScale(scaleFactor, scaleFactor, detector?.focusX
          ?: 0f, detector?.focusY ?: 0f)
      postInvalidate()
      scale = curScale
      return true
    }

    override fun onScaleEnd(detector: ScaleGestureDetector?)
    {
      super.onScaleEnd(detector)
    }
  }
Simple OnScale GestureListener 의 onScale 방법 으로 크기 조정 을 처리 하고 크기 조정 인 자 를 transMatrix 에 적용 하 며 post Invaidate()를 호출 하여 다시 그립 니 다.
다음 포 인 트 는 바로 onDraw 방법 입 니 다.

override fun onDraw(canvas: Canvas?)
  {
    canvas?.let {
      val saveState = it.saveCount
      it.save()
      it.concat(transMatrix)
      super.onDraw(canvas)
      it.restoreToCount(saveState)
      drawMask(it)
      drawRect(it)

    }
  }
먼저 save 를 호출 하여 현재 캔버스 상 태 를 저장 한 다음 에 transMatrix 를 사용 하여 캔버스 를 확대 하고 이동 한 다음 에 ImageView 의 onDraw()방법,즉 부모 클래스 의 방법 으로 그림 을 그립 니 다.커버 층 을 그립 니 다.재단 상자 가 이동 하지 않 기 때문에 캔버스 상 태 를 회복 한 후에 그립 니 다.
마지막 으로 그림 을 자 르 는 거 예요.

inner class SaveTask(private val filePath: String) : AsyncTask<Unit, Unit, Unit>()
  {

    override fun doInBackground(vararg params: Unit?): Unit
    {
      saveClipImage(filePath)

    }

    override fun onPostExecute(result: Unit?)
    {
      super.onPostExecute(result)
      onsaveClipImageListener?.onImageFinishedSav()
    }
  }


  fun clipAndSaveImage(filePath: String)
  {
    SaveTask(filePath).execute()
  }

  private fun saveClipImage(filePath: String)
  {
    val clipBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val clipCanvas = Canvas(clipBitmap)
    draw(clipCanvas)
    try
    {
      val outputStream = FileOutputStream(filePath)
      val bitmap = Bitmap.createBitmap(clipBitmap, width / 2 - clipWidth / 2, height / 2 - clipHeight / 2, clipWidth, clipHeight, transMatrix, true)
      bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
      outputStream.close()
    }
    catch (e: IOException)
    {
      e.printStackTrace()
    }

  }
비트 맵 을 자 르 고 저장 하기 위해 AsyncTask 를 시작 한 것 을 볼 수 있 습 니 다.그 중에서 saveClipImage 는 캔버스 를 다시 구축 하고 bitmap 에 전송 하 며 draw 방법 을 다시 호출 하여 데이터 정 보 를 bitmap 에 저장 한 다음 bitmap 를 자 르 고 파일 에 저장 합 니 다.
3.소스 주소GitHub
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기