Android浮动活动与滑动关闭

Posted

技术标签:

【中文标题】Android浮动活动与滑动关闭【英文标题】:Android Floating activity with dismiss on swipe 【发布时间】:2015-01-30 13:20:11 【问题描述】:

最新的 Facebook 安卓应用有一个非常漂亮的浮动评论窗口。在那里,用户可以关闭向上或向下滑动的窗口,使其真正易于使用。

我想在我的应用中实现类似的行为,但我不知道该怎么做。任何关于如何做的想法或线索将不胜感激。

Facebook 应用程序的屏幕截图 (抱歉,我截取屏幕截图的 Facebook 应用程序是日文的)

【问题讨论】:

我用几张截图更新了我的答案;) 【参考方案1】:

我编写了一些与这种关闭/调整大小行为相匹配的代码,我不知道这是否可行,但我的代码基于 Activity 类。我要做的第一件事是创建一个活动并给它Transluscenttheme 以获得具有透明背景的活动。

在我的 manifest.xml 中:

<activity
    android:name=".PopupActivity"
    android:label="@string/title_activity_popup"
    <!-- Use Translucent theme to get transparent activity background 
     and NoTitleBar to avoid super old style title bar ;) -->
    android:theme="@android:style/Theme.Translucent.NoTitleBar">
</activity>

然后我创建一个简单的布局文件,其中包含一个文本视图(对应于 Facebook 聊天部分)和一个视图(对应于 Facebook“写你的消息”/“发送笑脸”选项​​卡)

我的布局/activity_popup.xml:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/base_popup_layout"
    android:layout_
    android:layout_
    android:padding="8dp"
    android:background="@android:color/darker_gray"
    android:layout_marginBottom="124dp">

    <TextView
        android:text="@string/hello_world"
        android:layout_
        android:layout_
        android:background="@android:color/black"/>

    <View
        android:layout_
        android:layout_
        android:layout_alignParentBottom="true"
        android:background="@android:color/holo_blue_dark"/>

</RelativeLayout>

最后我在 PopupActivity 类中处理触摸和移动事件,我使用onTouchListener 在 onTouch 方法中提供回调。

PopupActivity

public class PopupActivity extends Activity implements View.OnTouchListener

    private RelativeLayout baseLayout;

    private int previousFingerPosition = 0;
    private int baseLayoutPosition = 0;
    private int defaultViewHeight;

    private boolean isClosing = false;
    private boolean isScrollingUp = false;
    private boolean isScrollingDown = false;

    @Override
    protected void onCreate(Bundle savedInstanceState)
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_popup);
        baseLayout = (RelativeLayout) findViewById(R.id.base_popup_layout);
        baseLayout.setOnTouchListener(this);
    


    public boolean onTouch(View view, MotionEvent event) 

        // Get finger position on screen
        final int Y = (int) event.getRawY();

        // Switch on motion event type
        switch (event.getAction() & MotionEvent.ACTION_MASK) 

            case MotionEvent.ACTION_DOWN:
                // save default base layout height
                defaultViewHeight = baseLayout.getHeight();

                // Init finger and view position
                previousFingerPosition = Y;
                baseLayoutPosition = (int) baseLayout.getY();
                break;

            case MotionEvent.ACTION_UP:
                // If user was doing a scroll up
                if(isScrollingUp)
                    // Reset baselayout position
                    baseLayout.setY(0);
                    // We are not in scrolling up mode anymore
                    isScrollingUp = false;
                

                // If user was doing a scroll down
                if(isScrollingDown)
                    // Reset baselayout position
                    baseLayout.setY(0);
                    // Reset base layout size
                    baseLayout.getLayoutParams().height = defaultViewHeight;
                    baseLayout.requestLayout();
                    // We are not in scrolling down mode anymore
                    isScrollingDown = false;
                
                break;
            case MotionEvent.ACTION_MOVE:
                if(!isClosing)
                    int currentYPosition = (int) baseLayout.getY();

                    // If we scroll up
                    if(previousFingerPosition >Y)
                        // First time android rise an event for "up" move
                        if(!isScrollingUp)
                            isScrollingUp = true;
                        

                    // Has user scroll down before -> view is smaller than it's default size -> resize it instead of change it position
                    if(baseLayout.getHeight()<defaultViewHeight)
                        baseLayout.getLayoutParams().height = baseLayout.getHeight() - (Y - previousFingerPosition);
                        baseLayout.requestLayout();
                    
                    else 
                        // Has user scroll enough to "auto close" popup ?
                        if ((baseLayoutPosition - currentYPosition) > defaultViewHeight / 4) 
                            closeUpAndDismissDialog(currentYPosition);
                            return true;
                        

                        //
                    
                    baseLayout.setY(baseLayout.getY() + (Y - previousFingerPosition));

                
                // If we scroll down
                else

                    // First time android rise an event for "down" move
                    if(!isScrollingDown)
                        isScrollingDown = true;
                    

                    // Has user scroll enough to "auto close" popup ?
                    if (Math.abs(baseLayoutPosition - currentYPosition) > defaultViewHeight / 2)
                    
                        closeDownAndDismissDialog(currentYPosition);
                        return true;
                    

                    // Change base layout size and position (must change position because view anchor is top left corner)
                    baseLayout.setY(baseLayout.getY() + (Y - previousFingerPosition));
                    baseLayout.getLayoutParams().height = baseLayout.getHeight() - (Y - previousFingerPosition);
                    baseLayout.requestLayout();
                

                // Update position
                previousFingerPosition = Y;
            
            break;
        
        return true;
    

当用户滚动到足以关闭弹出窗口(即动画和完成活动)时,会调用两个小方法:

public void closeUpAndDismissDialog(int currentPosition)
    isClosing = true;
    ObjectAnimator positionAnimator = ObjectAnimator.ofFloat(baseLayout, "y", currentPosition, -baseLayout.getHeight());
    positionAnimator.setDuration(300);
    positionAnimator.addListener(new Animator.AnimatorListener()
    
        . . .
        @Override
        public void onAnimationEnd(Animator animator)
        
            finish();
        
        . . .
    );
    positionAnimator.start();


public void closeDownAndDismissDialog(int currentPosition)
    isClosing = true;
    Display display = getWindowManager().getDefaultDisplay();
    Point size = new Point();
    display.getSize(size);
    int screenHeight = size.y;
    ObjectAnimator positionAnimator = ObjectAnimator.ofFloat(baseLayout, "y", currentPosition, screenHeight+baseLayout.getHeight());
    positionAnimator.setDuration(300);
    positionAnimator.addListener(new Animator.AnimatorListener()
     
        . . .
        @Override
        public void onAnimationEnd(Animator animator)
        
            finish();
        
        . . .
    );
    positionAnimator.start();

使用所有这些代码,您应该能够启动全局匹配 Facebook 弹出窗口行为的 PopupActivity。这只是一个草稿类,还有很多工作要做:添加动画、处理关闭参数等等......

截图:

【讨论】:

不错的答案,但如果视图具有滚动属性,则它不起作用 @Shah 在你想要交换时禁用滚动 @Gaëtan Maisse 如何禁用左右滑动通话 如何禁用向上滑动和滚动布局?谢谢 任何滚动布局的解决方案,例如:recyclerview?【参考方案2】:

我认为您可以使用 appcompat 库中的 BottomSheetDialogFragment 组件。查看这篇文章以获取信息: https://medium.com/@nullthemall/new-bottomsheet-caab21aff19b#.gpu1l516z

您还可以从以下网站获取有用信息 documentaion.

【讨论】:

【参考方案3】:

好吧,OP 标题要求浮动活动,但 OP 内容正在寻找类似于 facebook 评论窗口的 浮动评论窗口

因此,这将由 DialogFragment 实现,它为我们提供了在您向上或向下滑动时自动将对话框弹回其原始状态/窗口大小的行为。当对话框被滑动一点(精确地滑动小于原始布局大小的一半的距离)时,这种行为会保持。

剩下的事情是如果它的大小小于原始窗口大小的一半,它就会关闭这个对话框;换句话说,如果它的滑动大于原始布局大小的一半。这部分是通过更改对话框窗口根布局的唯一Y 位置来从接受的答案调整的,并且不更改窗口的大小,因为这提供了奇怪的调整大小行为。

首先创建此样式以使对话窗口具有透明背景:

<style name="NoBackgroundDialogTheme" parent="Theme.AppCompat.Light.Dialog">
    <item name="android:windowBackground">@null</item>
</style>

此样式将通过覆盖 getTheme() 方法应用于 DialogFragment。

这是自定义的DialogFragment:


class MyDialogFragment : DialogFragment(), View.OnTouchListener 

    private var rootLayoutY: Int = 0
    private val rootLayout by lazy 
        requireView().findViewById<ConstraintLayout>(R.id.dialog_root)
    

    private var oldY = 0
    private var baseLayoutPosition = 0
    private var defaultViewHeight = 0
    private var isScrollingUp = false
    private var isScrollingDown = false

    override fun getTheme(): Int 
        return R.style.NoBackgroundDialogTheme
    

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View 
        val view: View = inflater.inflate(
            R.layout.fragment_dialog_facebook_comment, container,
            false
        )
        view.setBackgroundResource(R.drawable.rounded_background)

        return view
    

    override fun onStart() 
        super.onStart()
        // Making the dialog full screen
        dialog?.window?.setLayout(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT
        )
    

    @SuppressLint("ClickableViewAccessibility")
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
        super.onViewCreated(view, savedInstanceState)
        rootLayout.setOnTouchListener(this)

        rootLayout.viewTreeObserver.addOnGlobalLayoutListener(object :
            OnGlobalLayoutListener 
            override fun onGlobalLayout() 
                rootLayout.viewTreeObserver
                    .removeOnGlobalLayoutListener(this)
                // save default base layout height
                defaultViewHeight = rootLayout.height
            
        )

    

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouch(v: View?, event: MotionEvent?): Boolean 
        // Get finger position on screen
        val y = event!!.rawY.toInt()


        // Switch on motion event type
        when (event.action and MotionEvent.ACTION_MASK) 

            MotionEvent.ACTION_DOWN -> 

                // Init finger and view position
                oldY = y
                baseLayoutPosition = rootLayout.y.toInt()
            

            MotionEvent.ACTION_UP -> 

                if (rootLayoutY >= defaultViewHeight / 2) 
                    dismiss()
                    return true
                

                // If user was doing a scroll up
                if (isScrollingUp) 
                    // Reset baselayout position
                    rootLayout.y = 0f
                    // We are not in scrolling up mode anymore
                    isScrollingUp = false
                

                // If user was doing a scroll down
                if (isScrollingDown) 
                    // Reset baselayout position
                    rootLayout.y = 0f
                    //  Reset base layout size
                    rootLayout.layoutParams.height = defaultViewHeight
                    rootLayout.requestLayout()
                    // We are not in scrolling down mode anymore
                    isScrollingDown = false
                

            

            MotionEvent.ACTION_MOVE -> 

                rootLayoutY = abs(rootLayout.y.toInt())

                // Change base layout size and position (must change position because view anchor is top left corner)
                rootLayout.y = rootLayout.y + (y - oldY)

                if (oldY > y)  // scrolling up
                    if (!isScrollingUp) isScrollingUp = true
                 else  // Scrolling down
                    if (!isScrollingDown) isScrollingDown = true
                

                // Update y position
                oldY = y
            
        
        return true
    



在我的例子中,对话框的根布局是ConstraintLayout

【讨论】:

以上是关于Android浮动活动与滑动关闭的主要内容,如果未能解决你的问题,请参考以下文章

Android滑动到顶部悬停

Android 浮动按钮的伸缩效果

如何使用 android 活动转换为浮动操作按钮设置动画?

关闭浮动操作按钮 Android

使用 Android 设计支持库中的浮动操作按钮的浮动操作菜单实现 [关闭]

Android:在视图/活动/片段之间滑动切换