如何在聊天应用中制作向左滑动回复动画?

问题描述 投票:0回答:2

我正在开发一个即时通讯项目,即将完成。但我想制作一个通过滑动回复特定消息的功能。我搜索了一下,发现了一篇很棒的文章。所以我刚刚实现了它并且它按预期工作了。

现在的问题是如何使其适用于右侧消息,向左滑动进行回复。或者我们可以说与平常相反。我只是想让它看起来像 WhatsApp 一样专业。

我已经尝试过这种方式,但它只是向左滑动,没有回复动画没有振动。 源代码

override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
    mView = viewHolder.itemView
    imageDrawable = context.getDrawable(R.drawable.ic_reply_black_24dp)!!
    shareRound = context.getDrawable(R.drawable.ic_round_shape)!!
    val direction = if (viewHolder.itemViewType != MessageType.SEND) {
        RIGHT
    } else {
        LEFT
    }
    return ItemTouchHelper.Callback.makeMovementFlags(ACTION_STATE_IDLE, direction)
}
android kotlin animation gesture
2个回答
1
投票
public class SwipeReply extends ItemTouchHelper.Callback {

    private Drawable imageDrawable;
    private Drawable shareRound;
    private RecyclerView.ViewHolder currentItemViewHolder;
    private View mView;
    private float dX = 0f;
    private float replyButtonProgress = 0f;
    private long lastReplyButtonAnimationTime = 0;
    private boolean swipeBack = false;
    private boolean isVibrate = false;
    private boolean startTracking = false;
    private float density;
    private final Context context;
    private final SwipeControllerActions swipeControllerActions;
    public SwipeReply(@NotNull Context context, @NotNull SwipeControllerActions swipeControllerActions) {
        super();
        this.context = context;
        this.swipeControllerActions = swipeControllerActions;
        this.density = 1.0F;
    }
    @Override
    public int getMovementFlags(@NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        mView = viewHolder.itemView;
        if(viewHolder.getItemViewType()== MessageAdapter.RECEIVER_VIEW_TYPE||viewHolder.getItemViewType()== MessageAdapter.RECEIVER_VIEW_IMAGE||viewHolder.getItemViewType()== MessageAdapter.RECEIVED_MESSAGE_IMAGE){
            imageDrawable = context.getDrawable(R.drawable.ic_reply);
            shareRound = context.getDrawable(R.drawable.ic_round);
            return ItemTouchHelper.Callback.makeMovementFlags(ACTION_STATE_IDLE, RIGHT);
        }
        if(viewHolder.getItemViewType()== MessageAdapter.SENDER_VIEW_TYPE||viewHolder.getItemViewType()== MessageAdapter.SENDER_VIEW_IMAGE||viewHolder.getItemViewType()== MessageAdapter.SENDER_MESSAGE_IMAGE){
            imageDrawable = context.getDrawable(R.drawable.ic_reply);
            shareRound = context.getDrawable(R.drawable.ic_round);
            return ItemTouchHelper.Callback.makeMovementFlags(ACTION_STATE_IDLE, LEFT);
        }

        return 0;
    }
    @Override
    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        return false;
    }
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    }
    @Override
    public int convertToAbsoluteDirection(int flags, int layoutDirection) {
        if (swipeBack) {
            swipeBack = false;
            return 0;
        }
        return super.convertToAbsoluteDirection(flags, layoutDirection);
    }
    @Override
    public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        if (actionState == ACTION_STATE_SWIPE) {
            setTouchListener(recyclerView, viewHolder);
        }
        super.onChildDraw(c, recyclerView, viewHolder, dX/2, dY, actionState, isCurrentlyActive);
        this.dX = dX;
        startTracking = true;
        currentItemViewHolder = viewHolder;
        drawReplyButton(c);
    }
    @SuppressLint("ClickableViewAccessibility")
    private void setTouchListener(RecyclerView recyclerView, final RecyclerView.ViewHolder viewHolder) {
        recyclerView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                swipeBack = motionEvent.getAction() == MotionEvent.ACTION_CANCEL || motionEvent.getAction() == MotionEvent.ACTION_UP;
                if (swipeBack) {
                    if (Math.abs(mView.getTranslationX()) >=convertTodp(80)) {
                        swipeControllerActions.showReplyUI(viewHolder.getAdapterPosition());
                    }
                }
                return false;
            }
        });
    }
    private void drawReplyButton(Canvas canvas) {
        if (currentItemViewHolder == null) {
            return;
        }
        float translationX = mView.getTranslationX();
        long newTime = System.currentTimeMillis();
        long dt = Math.min(17, newTime - lastReplyButtonAnimationTime)/2;
        lastReplyButtonAnimationTime = newTime;
        boolean showing = translationX >= convertTodp(30);
        boolean showing1 = translationX <=-convertTodp(30);
        if (showing|showing1) {
            if (replyButtonProgress < 1.0f) {
                replyButtonProgress += dt / 180.0f;
                if (replyButtonProgress > 1.0f) {
                    replyButtonProgress = 1.0f;
                } else {
                    mView.invalidate();
                }
            }
        } else if (translationX == 0.0f) {
            replyButtonProgress = 0f;
            startTracking = false;
            isVibrate = false;
        }else {
            if (replyButtonProgress > 0.0f) {
                replyButtonProgress -= dt / 180.0f;
                if (replyButtonProgress < 0.1f) {
                    replyButtonProgress = 0f;
                } else {
                    mView.invalidate();
                }
            }
        }
        int alpha;
        float scale;
        if (showing||showing1) {
            scale = this.replyButtonProgress <= 0.8F ? 1.2F * (this.replyButtonProgress / 0.8F) : 1.2F - 0.2F * ((this.replyButtonProgress - 0.8F) / 0.2F);
            alpha = (int) Math.min(255.0F, (float) 255 * (this.replyButtonProgress / 0.8F));
        } else {
            scale = this.replyButtonProgress;
            alpha = (int) Math.min(255.0F, (float) 255 * this.replyButtonProgress);
        }
        shareRound.setAlpha(alpha);
        imageDrawable.setAlpha(alpha);
        if (startTracking) {
            if (!isVibrate && (mView.getTranslationX() >= convertTodp(80)||mView.getTranslationX() <= -convertTodp(80))) {
                mView.performHapticFeedback(
                        HapticFeedbackConstants.KEYBOARD_TAP,
                        HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING
                );
                isVibrate = true;
            }
        }

        int x;
        float y;
        y = (float) ((mView.getTop() + mView.getMeasuredHeight() / 2));
        if(mView.getTranslationX()>0){
            if (mView.getTranslationX() > (float) this.convertTodp(130)) {
                x = this.convertTodp(130) / 2;
            }else {
                x = (int) (mView.getTranslationX() / (float) 2);
            }
            shareRound.setBounds((int) ((float) x - (float) this.convertTodp(16) * scale), (int) (y - (float) this.convertTodp(16) * scale), (int) ((float) x + (float) this.convertTodp(16) * scale), (int) (y + (float) this.convertTodp(16) * scale));
            shareRound.draw(canvas);
            imageDrawable.setBounds((int) ((float) x - (float) this.convertTodp(10) * scale), (int) (y - (float) this.convertTodp(10) * scale), (int) ((float) x + (float) this.convertTodp(10) * scale), (int) (y + (float) this.convertTodp(8) * scale));
            imageDrawable.draw(canvas);
            shareRound.setAlpha(255);
            imageDrawable.setAlpha(255);
        }
        else if(0>mView.getTranslationX()){
            if (mView.getTranslationX() < -(float) this.convertTodp(130)) {
                x = mView.getRight()+(int) (mView.getTranslationX() / (float) 2);
            }else {
                x = mView.getRight()+(int) (mView.getTranslationX() / (float) 2);
            }
            shareRound.setBounds((int) ((float) x - (float) this.convertTodp(16) * scale), (int) (y - (float) this.convertTodp(16) * scale), (int) ((float) x + (float) this.convertTodp(16) * scale), (int) (y + (float) this.convertTodp(16) * scale));
            shareRound.draw(canvas);
            imageDrawable.setBounds((int) ((float) x - (float) this.convertTodp(10) * scale), (int) (y - (float) this.convertTodp(10) * scale), (int) ((float) x + (float) this.convertTodp(10) * scale), (int) (y + (float) this.convertTodp(8) * scale));
            imageDrawable.draw(canvas);
            shareRound.setAlpha(255);
            imageDrawable.setAlpha(255);

        }
    }


    private int convertTodp(int pixel) {
        return this.dp((float) pixel, this.context);
    }


    public int dp(Float value, Context context) {
        if (this.density == 1.0F) {
            this.checkDisplaySize(context);
        }

        return value == 0.0F ? 0 : (int) Math.ceil((double) (this.density * value));
    }

    private void checkDisplaySize(Context context) {
        try {
            this.density = context.getResources().getDisplayMetrics().density;
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public interface SwipeControllerActions {
        void showReplyUI(int position);
    }

}


0
投票

Kotlin 版本

class SwipeReply(private val context: Context, private val swipeControllerActions: SwipeControllerActions) :
ItemTouchHelper.Callback() {

private var replyDrawable: Drawable? = null
private var currentItemViewHolder: RecyclerView.ViewHolder? = null
private var mView: View? = null
private var dX = 0f
private var replyButtonProgress = 0f
private var lastReplyButtonAnimationTime: Long = 0
private var swipeBack = false
private var isVibrate = false
private var startTracking = false
private val density: Float = context.resources.displayMetrics.density

private val SWIPE_THRESHOLD = convertToDp(40)
private val DRAWABLE_SIZE = convertToDp(16)
private val MAX_TRANSLATION = convertToDp(80)

@SuppressLint("UseCompatLoadingForDrawables")
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
    mView = viewHolder.itemView
    replyDrawable = context.getDrawable(R.drawable.share_small)

    return when (viewHolder.itemViewType) {
        C.ONE_MESSAGE_TYPE.OTHER_USER.value, C.ONE_MESSAGE_TYPE.CURRENT_USER.value -> makeMovementFlags(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.RIGHT)
        else -> 0
    }
}

override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean = false

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}

override fun convertToAbsoluteDirection(flags: Int, layoutDirection: Int): Int {
    if (swipeBack) {
        swipeBack = false
        return 0
    }
    return super.convertToAbsoluteDirection(flags, layoutDirection)
}

override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
    if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
        setTouchListener(recyclerView, viewHolder)
    }
    super.onChildDraw(c, recyclerView, viewHolder, dX / 2, dY, actionState, isCurrentlyActive)
    this.dX = dX
    startTracking = true
    currentItemViewHolder = viewHolder
    drawReplyButton(c)
}

@SuppressLint("ClickableViewAccessibility")
private fun setTouchListener(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
    recyclerView.setOnTouchListener { _, motionEvent ->
        swipeBack = motionEvent.action == MotionEvent.ACTION_CANCEL || motionEvent.action == MotionEvent.ACTION_UP
        if (swipeBack && abs(mView!!.translationX) >= SWIPE_THRESHOLD) {
            swipeControllerActions.showReplyUI(viewHolder.adapterPosition)
        }
        false
    }
}

private fun drawReplyButton(canvas: Canvas) {
    currentItemViewHolder ?: return
    val translationX = mView!!.translationX
    val newTime = System.currentTimeMillis()
    val dt = min(17, newTime - lastReplyButtonAnimationTime) / 2
    lastReplyButtonAnimationTime = newTime
    val showing = translationX >= convertToDp(30)
    val showing1 = translationX <= -convertToDp(30)
    updateReplyButtonProgress(showing, showing1, dt.toFloat())
    val (alpha, scale) = calculateAlphaAndScale(showing, showing1)
    replyDrawable?.alpha = alpha
    if (startTracking) {
        checkVibration()
    }
    drawReplyDrawable(canvas, scale)
}

private fun updateReplyButtonProgress(showing: Boolean, showing1: Boolean, dt: Float) {
    when {
        showing || showing1 -> {
            if (replyButtonProgress < 1.0f) {
                replyButtonProgress += dt / 180.0f
                if (replyButtonProgress > 1.0f) {
                    replyButtonProgress = 1.0f
                } else {
                    mView?.invalidate()
                }
            }
        }
        mView!!.translationX == 0.0f -> {
            replyButtonProgress = 0f
            startTracking = false
            isVibrate = false
        }
        else -> {
            if (replyButtonProgress > 0.0f) {
                replyButtonProgress -= dt / 180.0f
                if (replyButtonProgress < 0.1f) {
                    replyButtonProgress = 0f
                } else {
                    mView?.invalidate()
                }
            }
        }
    }
}

private fun calculateAlphaAndScale(showing: Boolean, showing1: Boolean): Pair<Int, Float> {
    val alpha: Int
    val scale: Float
    if (showing || showing1) {
        scale = if (replyButtonProgress <= 0.8f) 1.2f * (replyButtonProgress / 0.8f) else 1.2f - 0.2f * ((replyButtonProgress - 0.8f) / 0.2f)
        alpha = min(255.0f, 255f * (replyButtonProgress / 0.8f)).toInt()
    } else {
        scale = replyButtonProgress
        alpha = min(255.0f, 255f * replyButtonProgress).toInt()
    }
    return Pair(alpha, scale)
}

private fun checkVibration() {
    if (!isVibrate && (mView!!.translationX >= SWIPE_THRESHOLD || mView!!.translationX <= -SWIPE_THRESHOLD)) {
        Functions.vibrateMedium(context)
        isVibrate = true
    }
}

private fun drawReplyDrawable(canvas: Canvas, scale: Float) {
    val y = (mView!!.top + mView!!.measuredHeight / 2).toFloat()
    val x = calculateDrawablePosition()
    replyDrawable?.setBounds(
        (x.toFloat() - DRAWABLE_SIZE.toFloat() * scale).toInt(),
        (y - DRAWABLE_SIZE.toFloat() * scale).toInt(),
        (x.toFloat() + DRAWABLE_SIZE.toFloat() * scale).toInt(),
        (y + DRAWABLE_SIZE.toFloat() * scale).toInt()
    )
    replyDrawable?.draw(canvas)
    replyDrawable?.alpha = 255
}

private fun calculateDrawablePosition(): Int {
    return when {
        mView!!.translationX > 0 -> {
            if (mView!!.translationX > MAX_TRANSLATION.toFloat()) {
                MAX_TRANSLATION / 2
            } else {
                (mView!!.translationX / 2f).toInt()
            }
        }
        0 > mView!!.translationX -> {
            if (mView!!.translationX < -MAX_TRANSLATION.toFloat()) {
                mView!!.right + (mView!!.translationX / 2f).toInt()
            } else {
                mView!!.right + (mView!!.translationX / 2f).toInt()
            }
        }
        else -> 0
    }
}

private fun convertToDp(pixel: Int): Int {
    return dp(pixel.toFloat())
}

private fun dp(value: Float): Int {
    return if (value == 0.0f) 0 else ceil((density * value).toDouble()).toInt()
}

interface SwipeControllerActions {
    fun showReplyUI(position: Int)
}
}

使用方法:

    val messageSwipeController = SwipeReply(requireContext(), this)
    val itemTouchHelper = ItemTouchHelper(messageSwipeController)
    itemTouchHelper.attachToRecyclerView(binding.mainRv)
© www.soinside.com 2019 - 2024. All rights reserved.