안 드 로 이 드 모방 위 챗 애플 릿 입구 애니메이션

효과 대비
위 챗 원판

모방 효과

공정 분석
사용자 정의 ViewGroup
전체 레이아웃 은 사용자 정의 ViewGroup 을 통 해 관 리 됩 니 다.사용자 정의 ViewGroup 에서 하위 레이아웃 은 모두 두 개 입 니 다.하 나 는 애플 릿 레이아웃 이 고 하 나 는 세 션 목록 레이아웃 입 니 다.그리고 상하 로 나 누 면 됩 니 다.

package com.example.kotlindemo.widget.weixin

import android.content.Context
import android.content.res.Resources
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.customview.widget.ViewDragHelper
import com.example.kotlindemo.R
import java.math.BigDecimal

class WeiXinMainPullViewGroup @JvmOverloads constructor(
    context: Context?,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {

    public var viewDragHelper: ViewDragHelper = ViewDragHelper.create(this, 0.5f, DragHandler());

    var headerMaskView: WeiXinPullHeaderMaskView? = null

    var isOpen: Boolean = false;

    val NAVIGAATION_HEIGHT = 100

    init {

    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {

        for (index in 0 until childCount) {
            if (getChildAt(index) != headerMaskView) {
                getChildAt(index).layout(l, paddingTop, r, b)
            }
        }

    }

    override fun computeScroll() {
        if (viewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }

    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        Log.i("TAG", "onInterceptTouchEvent: ${ev.action}")
        MotionEvent.ACTION_MOVE
        return true
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {

        viewDragHelper.processTouchEvent(event)
        return true
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        measureChildren(widthMeasureSpec, heightMeasureSpec)
    }

    fun createMaskView() {
        if (headerMaskView == null) {
            headerMaskView = WeiXinPullHeaderMaskView(context, null, 0)
            addView(headerMaskView)
        }
    }

    inner class DragHandler : ViewDragHelper.Callback() {
        override fun tryCaptureView(child: View, pointerId: Int): Boolean {
            return child is WeiXinMainLayout;
        }

        override fun onViewDragStateChanged(state: Int) {
            super.onViewDragStateChanged(state)
        }

        /**
         *     ,    layout
         */
        override fun onViewPositionChanged(
            changedView: View,
            left: Int,
            top: Int,
            dx: Int,
            dy: Int
        ) {
            createMaskView();
            var programView = getChildAt(0)

            var divide = BigDecimal(top.toString()).divide(
                BigDecimal(measuredHeight - NAVIGAATION_HEIGHT),
                4,
                BigDecimal.ROUND_HALF_UP
            )
            divide = divide.multiply(BigDecimal("100"))

            divide = divide.multiply(BigDecimal("0.002"))
            divide = divide.add(BigDecimal("0.8"))

            if (!isOpen) {
                programView.scaleX = divide.toFloat()
                programView.scaleY = divide.toFloat()
            } else {
                programView.top = paddingTop + (-((measuredHeight - NAVIGAATION_HEIGHT) - top))
            }

            headerMaskView!!.maxHeight = measuredHeight / 3
            headerMaskView!!.layout(0, paddingTop, measuredWidth, top)
            headerMaskView!!.setProgress(
                top.toFloat() / ((measuredHeight - (NAVIGAATION_HEIGHT + paddingTop)) / 3) * 100,
                measuredHeight - (NAVIGAATION_HEIGHT + paddingTop)
            )
            if (top == paddingTop) {
                isOpen = false
            }
            if (top == measuredHeight - NAVIGAATION_HEIGHT) {
                isOpen = true
            }

        }

        override fun onViewCaptured(capturedChild: View, activePointerId: Int) {
            super.onViewCaptured(capturedChild, activePointerId)
            var programView = getChildAt(0)
            programView.top = paddingTop;
        }

        /**
         *   
         */
        override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {

            /**
             *                    ,    
             */
            if (isOpen or (releasedChild.top + paddingTop <= measuredHeight / 3)) {
                viewDragHelper.smoothSlideViewTo(releasedChild, 0, paddingTop);
                ViewCompat.postInvalidateOnAnimation(this@WeiXinMainPullViewGroup);
                return
            }
            viewDragHelper.smoothSlideViewTo(releasedChild, 0, measuredHeight - NAVIGAATION_HEIGHT);
            ViewCompat.postInvalidateOnAnimation(this@WeiXinMainPullViewGroup);
        }

        override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
            if (top <= paddingTop) {
                return paddingTop
            }
            return (child.top + dy / 1.3).toInt();
        }

    }
}
상태 표시 줄 을 채 우 는 View 도 추가 해 야 합 니 다.그의 높이 는 동적 으로 가 져 온 것 입 니 다.전체적인 구 조 는 RelativeLayout 입 니 다.중간 View 를 상태 아래 와 네 비게 이 션 표시 줄 위 에 편리 하 게 설정 할 수 있 기 때 문 입 니 다.

class ViewUtils {
    companion object{
        @JvmStatic
        fun getStatusBarHeight(resources: Resources): Int {
            var result = 0
            val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
            if (resourceId > 0) {
                result = resources.getDimensionPixelSize(resourceId)
            }
            return result
        }
    }
}

애플 릿 크기 조정 비율 값 계산
그 다음 에 해 야 할 일 은 View 를 드래그 하 는 것 입 니 다.View DragHelper 를 통 해 완성 할 수 있 습 니 다.세 션 레이아웃 을 드래그 할 때 작은 프로그램의 레이아웃 은 크기 조정 비례 애니메이션 을 만 들 기 시 작 했 습 니 다.이 크기 조정 값 은 제 가 여기 서 이렇게 했 습 니 다.0 부터 시작 할 수 없 기 때문에 기본 값 부터 시작 해 야 합 니 다.이 기본 값 은 0.8 입 니 다.그러면 0.2 의 크기 조정 값 은 처음부터 계산 하 는 것 입 니 다.전체 높이 까지 의 백분율.
예 를 들 어 화면 높이 가 1000 이 고 500 까지 내 려 갈 때 이 크기 는 0.1 이 고 기본 값 0.8 을 더 하면 계산 방식 은 다음 과 같 으 며 전체 높이 는 네 비게 이 션 바 의 높이 를 빼 야 한다.

   var divide = BigDecimal(top.toString()).divide(BigDecimal(measuredHeight-NAVIGAATION_HEIGHT), 4, BigDecimal.ROUND_HALF_UP)
   divide = divide.multiply(BigDecimal("100"))
   divide = divide.multiply(BigDecimal("0.002" ))
   divide = divide.add(BigDecimal("0.8"))
   if (!isOpen) {
       programView.scaleX = divide.toFloat()
       programView.scaleY = divide.toFloat()
   } else {
       programView.top = paddingTop + (-((measuredHeight - NAVIGAATION_HEIGHT) - top))
   }
디 테 일 에 주의 하 세 요.아래로 끌 어 올 릴 때 애플 릿 레이아웃 은 크기 조정 을 통 해 나타 나 지만 위 에서 미끄러져 닫 힐 때 애플 릿 레이아웃 은 세 션 레이아웃 과 동시에 위로 올 라 갑 니 다.
애니메이션 마스크
이것 은 비교적 번 거 로 운 단계 이다.바로 진도 애니메이션,즉 그 세 개의 원점 을 그 리 는 것 이다.
이 원점 은 세 가지 상태 가 있 는데 하 나 는 어 릴 때 부터 크 고,다른 하 나 는 일정한 크기 에 이 르 러 두 개의 고정 크기 의 원 을 분리 하 는 것 이다.그러나 이 두 개의 원 은 이때 중간의 것 보다 작고,아래 의 진도 와 천천히 양쪽 으로 확대 하 는 것 이다.셋째,중간의 원 은 나머지 두 개의 원 과 같은 크기 로 축소 되 기 시작한다.
여기 서 또 다른 디 테 일이 필요 합 니 다.화면의 3 분 의 1 을 끌 어 내 릴 때 이 머리 는 전체적으로 불투명 하지만 화면의 3 분 의 1 에 이 르 렀 을 때 이 구조의 투명 도 는 255 에서 0 으로 움 직 이기 시 작 했 습 니 다.그리고 3 분 의 1 에 도 달 했 을 때 진동 을 해 야 하고 진동 만 지나 면 손가락 이 풀 리 지 않 았 을 때 다시 화면의 3 분 의 1 에 도 달 했 을 때 진동 이 일어나 지 않 는 다.
그리고 디 테 일 한 라운드 가 있 습 니 다.상태 표시 줄 은 View 로 채 워 졌 기 때문에 화면 3 부 중 하나 부터 이 View 의 투명 도 는 255-0 부터 운동 해 야 합 니 다.
전체 코드 는 다음 과 같다.

package com.example.kotlindemo.widget.weixin

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.VibrationEffect
import android.os.Vibrator
import android.util.AttributeSet
import android.util.Log
import android.view.View
import androidx.core.content.ContextCompat
import com.example.kotlindemo.MainActivity
import com.example.kotlindemo.R


class WeiXinPullHeaderMaskView @JvmOverloads constructor(
    context: Context?,
    attrs: AttributeSet?,
    defStyleAttr: Int
) :
    View(context, attrs, defStyleAttr) {

    var isVibrator: Boolean = false;
    var progress: Int = 0;
    var maxHeight: Int = 0;
    private val CIRCLE_MAX_SIZE = 32;
    var parentHeight=0;
    var paint = Paint()
    private val DEFAULT_CIRCLE_SIZE=8f;
    init {
        setBackgroundColor(Color.argb(255 , 239, 239, 239))
        paint.alpha=255;
        paint.color = ContextCompat.getColor(context!!, R.color.circleColor)
        paint.isAntiAlias = true;
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        var value = height.toFloat() / maxHeight

        if (height <= maxHeight / 2) {
            canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), CIRCLE_MAX_SIZE * value, paint)
        } else {
          if (progress<100){
              var diff = (value - 0.5f) * CIRCLE_MAX_SIZE
              canvas.drawCircle(((width / 2).toFloat()-((0.4f-value)*100)), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint)
              canvas.drawCircle(((width / 2).toFloat()+((0.4f-value)*100)), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint)
              if ((CIRCLE_MAX_SIZE * 0.5f) - diff<=DEFAULT_CIRCLE_SIZE){
                  canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint)
              }else{
                  canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), (CIRCLE_MAX_SIZE * 0.5f) - diff, paint)
              }

          }else{
              paint.alpha=getAlphaValue();
              canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint)
              canvas.drawCircle((width / 2).toFloat()-((0.4f)*100), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint)
              canvas.drawCircle((width / 2).toFloat()+(((0.4f)*100)), (height / 2).toFloat(), DEFAULT_CIRCLE_SIZE, paint)
          }
        }

    }
    private fun getAlphaValue():Int{
        val dc=parentHeight/3-ViewUtils.getStatusBarHeight(resources);
        val alpha=((height).toFloat()-dc)/(parentHeight-(dc))
       return 255-(255*alpha).toInt()
    }

    private fun vibrator() {
        var vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            var createOneShot = VibrationEffect.createOneShot(7, 255)
            vibrator.vibrate(createOneShot)
        } else {
            vibrator.vibrate(7)
        }
    }

    fun setProgress(value: Float,parentHeight:Int) {
        this.progress = value.toInt();
        this.parentHeight=parentHeight;
        if (value >= 100 && !isVibrator) {
            vibrator()
            isVibrator = true;
        }
        if (value < 100) {
            isVibrator = false;
        }
        if (progress>=100){
            setBackgroundColor(Color.argb(getAlphaValue() , 239, 239, 239))
            var mainActivity = context as MainActivity
            mainActivity.changeStatusBackgroundAlphaValue(getAlphaValue())
        }else{
            setBackgroundColor(Color.argb(255, 239, 239, 239))
        }
        invalidate()
    }


}
그리고 이 세 가지 원점 은 항상 마스크 뷰 중간 에 있 고 그 릴 때 중간 에 만 그 려 야 하 며 마스크 뷰 의 높이 는 외부 View 에 의 해 변 경 됩 니 다.
MainActivity

import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.Window
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.example.kotlindemo.databinding.ActivityMainBinding
import com.example.kotlindemo.widget.weixin.ChatSession
import com.example.kotlindemo.widget.weixin.ChatSessionAdapter
import com.example.kotlindemo.widget.weixin.ViewUtils


class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding;


    fun changeStatusBackgroundAlphaValue(value: Int){
        binding.statusBar.setBackgroundColor(Color.argb(value, 239, 239, 239))
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main);

        
        var layoutParams = binding.statusBar.layoutParams
        layoutParams.height=ViewUtils.getStatusBarHeight(resources)
        binding.statusBar.layoutParams=layoutParams
        binding.wxMain.setPadding(0, ViewUtils.getStatusBarHeight(resources), 0, 0)

        if (Build.VERSION.SDK_INT >= 21) {
            val window: Window = window
            window.getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
            )
            window.setStatusBarColor(Color.TRANSPARENT)
        }


        val chatSessions= mutableListOf<ChatSession>()

        for (index in 0 .. 10){
            chatSessions.add(ChatSession("https://img2.baidu.com/it/u=3538084390,1079314259&fm=26&fmt=auto&gp=0.jpg","  ","  ,      ","  "))
            chatSessions.add(ChatSession("https://img0.baidu.com/it/u=273576249,1042072491&fm=26&fmt=auto&gp=0.jpg","   ","     ","  "))
            chatSessions.add(ChatSession("https://img1.baidu.com/it/u=152902017,4157746361&fm=11&fmt=auto&gp=0.jpg","  ","    ","  "))
            chatSessions.add(ChatSession("https://img0.baidu.com/it/u=3789809038,289359647&fm=26&fmt=auto&gp=0.jpg","    ","    ","  "))

        }
        binding.chatList.adapter=ChatSessionAdapter(chatSessions,this)
    }

}

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <RelativeLayout
        android:background="@drawable/program_background"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">


        <com.example.kotlindemo.widget.weixin.WeiXinMainPullViewGroup
        android:paddingTop="40dp"
            android:layout_above="@+id/navigation"
            android:id="@+id/wx_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
          >
            <com.example.kotlindemo.widget.weixin.WeiXinProgram
                android:paddingLeft="30dp"
                android:paddingRight="30dp"
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <TextView
                    android:textSize="17sp"
                    android:textColor="#C8C8C8"
                    android:gravity="center"
                    android:text="  "
                    android:layout_width="match_parent"
                    android:layout_height="40dp"></TextView>

                <androidx.cardview.widget.CardView
                    android:background="#424459"
                    app:cardBackgroundColor="#424459"
                    app:cardElevation="0dp"
                    app:cardCornerRadius="8dp"
                    android:layout_width="match_parent"
                    android:layout_height="46dp">
                    <LinearLayout
                        android:gravity="center"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent">
                        <TextView
                            android:textSize="15sp"
                            android:textColor="#C8C8C8"
                            android:text="     "
                            android:gravity="center"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"></TextView>
                    </LinearLayout>
                </androidx.cardview.widget.CardView>
                <com.example.kotlindemo.widget.weixin.ProgramGridLayout
                    android:layout_marginTop="20dp"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                </com.example.kotlindemo.widget.weixin.ProgramGridLayout>
                <com.example.kotlindemo.widget.weixin.ProgramGridLayout
                    android:layout_marginTop="20dp"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                </com.example.kotlindemo.widget.weixin.ProgramGridLayout>
            </com.example.kotlindemo.widget.weixin.WeiXinProgram>
            <com.example.kotlindemo.widget.weixin.WeiXinMainLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">
                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="44dp"
                    android:background="@color/navigation_color">

                    <TextView
                        android:textStyle="bold"
                        android:textSize="16sp"
                        android:textColor="#000000"
                        android:layout_centerInParent="true"
                        android:gravity="center"
                        android:text="  (323)"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent"></TextView>
                    <ImageView
                        android:layout_marginRight="45dp"
                        android:scaleType="center"
                        android:layout_centerVertical="true"
                        android:layout_alignParentRight="true"
                        android:src="@drawable/ic_search"
                        android:layout_width="28dp"
                        android:layout_height="28dp"></ImageView>
                    <ImageView
                        android:layout_marginRight="10dp"
                        android:scaleType="center"
                        android:layout_centerVertical="true"
                        android:layout_alignParentRight="true"
                        android:src="@drawable/ic_add"
                        android:layout_width="28dp"
                        android:layout_height="28dp">

                    </ImageView>
                </RelativeLayout>
                <com.example.kotlindemo.widget.weixin.WeiXinChatSessionListView
                    android:paddingLeft="15dp"

                    android:paddingRight="15dp"
                    android:dividerHeight="10dp"
                    android:id="@+id/chat_list"
                    android:background="#FBFAFA"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">

                </com.example.kotlindemo.widget.weixin.WeiXinChatSessionListView>

            </com.example.kotlindemo.widget.weixin.WeiXinMainLayout>

        </com.example.kotlindemo.widget.weixin.WeiXinMainPullViewGroup>

        <LinearLayout
            android:background="@color/navigation_color"
            android:orientation="vertical"
            android:id="@+id/navigation"
            android:layout_alignParentBottom="true"
            android:layout_width="match_parent"
            android:layout_height="60dp">
        </LinearLayout>
        <View
            android:background="@color/navigation_color"
            android:id="@+id/status_bar"
            android:layout_width="match_parent"
            android:layout_height="100dp"></View>
    </RelativeLayout>
</layout>
이상 은 안 드 로 이 드 가 위 챗 애플 릿 입구 애니메이션 을 모방 한 상세 한 내용 입 니 다.안 드 로 이 드 위 챗 애플 릿 입구 애니메이션 에 관 한 자 료 는 다른 관련 글 을 주목 하 십시오!

좋은 웹페이지 즐겨찾기