【问题标题】:How can achieve Gmail like pinch zoom and header scrolling如何实现像捏缩放和标题滚动这样的Gmail
【发布时间】:2020-09-16 06:03:29
【问题描述】:

如何实现像 Gmail 应用一样的捏缩放行为?我已将标题容器放在 ScrollView 中,然后是 WebView。似乎这是非常复杂的行为。

这里没有缩放。

当我们捏住 Webview 上部容器按缩放向上滚动时:

到目前为止,这是我的姓名缩写:

  <?xml version="1.0" encoding="utf-8"?>
  <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@color/white">

    <RelativeLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:background="@color/white"
       android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/white">

        </android.support.v7.widget.Toolbar>
    </android.support.design.widget.AppBarLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/appbar">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:background="@color/colorPrimary"></FrameLayout>

            <WebView
                android:id="@+id/webView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/white"
                android:scrollbars="none" />
        </LinearLayout>
    </ScrollView>
  </RelativeLayout>
</FrameLayout>

【问题讨论】:

  • 您能否检查我的答案并告诉我是否有帮助?谢谢。 :)

标签: android webview pinchzoom


【解决方案1】:

GMail 使用 Chrome WebView 并启用了缩放缩放功能。缩放仅适用于单线程视图。 WebSettings setBuiltInZoomControls() 默认为falsesetDisplayZoomControls() 默认为true。通过更改两者,缩放工作并且没有显示缩放控件:

webview.getSettings().setBuiltInZoomControls(true);
webview.getSettings().setDisplayZoomControls(false);

该工具栏是一个透明样式的ActionBar,样式为windowActionBarOverlay,设置为true

指示此窗口的操作栏是否应覆盖应用程序内容的标志。


ActionBar 的底部阴影在最顶部的滚动位置被移除。这个监听垂直滚动事件而不是任何缩放手势。这个效果的工作原理是这样的(最初必须隐藏阴影):

webView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
    @Override
    public void onScrollChange(View view, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        if(scrollY == 0) {
            /* remove the ActionBar's bottom shadow  */
        } else {
            /* apply the ActionBar's bottom shadow */
        }
    }
}

根据触发OnScrollChangeListener 的频率,即使检查scrollY == 0scrollY == 1 可能已经足以打开和关闭阴影。


当缩放时,这似乎是一个ScaleGestureDetector.SimpleOnScaleGestureListener(参见docs),其中.getScaleFactor() 用于动画辅助“工具栏”垂直顶部位置,然后将其推到可见视图之外-港口。这个辅助“工具栏”似乎是一个嵌套的垂直DrawerLayout - 无法手动移动 - 这就是它移动得如此平滑的原因......DrawerLayout 不仅限于水平抽屉;我认为这就是答案。

编辑:我现在比较确定,这是带有MDC Motion 的AndroidX。

【讨论】:

  • 这不是我要找的。如何放置标题,例如邮件标题容器按放大方式运行,请检查实际 gmail 应用程序的行为。我非常清楚内置缩放的 webview。
  • @AshishSahu 我已经检查了 GMail 应用程序,这准确地回答了您的问题 - 无论是否喜欢。你可能把事情复杂化了,这很简单。您只需将工具栏高度作为额外的填充添加到 HTML 中(服务器端 - 或使用 JavaScript 的客户端),以便工具栏永远不会覆盖加载的 HTML 内容。虽然很明显,HTML 内容最初不应位于半透明工具栏所在的位置。这是因为工具栏和 HTML 内容都需要从屏幕的最顶部开始。
  • 工具栏没有问题。请忘记工具栏的透明度。问题是当用户捏 webview 时,上面的标题/标题应该移开。我不知道如何正确地解释你。但是请忽略工具栏透明度的事情。
  • @AshishSahu 扩展了我的答案,现在也涵盖了这种效果。因为我使用了“显示布局边界”,所以我现在确定辅助工具栏是 RelativeLayout 的子工具栏,而不是 WebView 中的内容。
  • 该布局的根节点甚至是DrawerLayout ...因为导航抽屉在那里可用。与辅助工具栏的行为类似,这与DrawerLayoutNavigationView... 非常相似,只是垂直且位置控制不同;但它以同样的方式被推到可见视口之外。
【解决方案2】:

我想我理解了你的问题。您希望在展开电子邮件时将主题行向上推,而将其他电子邮件向下推。我试图实现在 Gmail 应用程序中显示电子邮件的想法。我认为我非常接近解决方案,因为推动不够顺畅。但是,我想在这里分享答案,以表达我对您问题的看法。

I have created a GitHub repository 从那里你可以看到我的实现。我还在那里添加了一个自述文件来解释整体想法。

我尝试使用具有不同ViewTypes 的RecyclerView 来实现整个事情。我添加了一个如下所示的适配器。

public class RecyclerViewWithHeaderFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int HEADER_VIEW = 1;
    private static final int GROUPED_VIEW = 2;
    private static final int EXPANDED_VIEW = 3;

    private ArrayList<Integer> positionTracker; // Take any list that matches your requirement.
    private Context context;
    private ZoomListener zoomListener;

    // Define a constructor
    public RecyclerViewWithHeaderFooterAdapter(Context context, ZoomListener zoomListener) {
        this.context = context;
        this.zoomListener = zoomListener;
        positionTracker = Utilities.populatePositionsWithDummyData();
    }

    // Define a ViewHolder for Header view
    public class HeaderViewHolder extends ViewHolder {
        public HeaderViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the item
                }
            });
        }
    }

    // Define a ViewHolder for Expanded view
    public class ExpandedViewHolder extends ViewHolder {
        public ExpandedViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the item
                }
            });
        }
    }

    // Define a ViewHolder for Expanded view
    public class GroupedViewHolder extends ViewHolder {
        public GroupedViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the item
                }
            });
        }
    }

    // And now in onCreateViewHolder you have to pass the correct view
    // while populating the list item.
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View v;

        if (viewType == EXPANDED_VIEW) {
            v = LayoutInflater.from(context).inflate(R.layout.list_item_expanded, parent, false);
            ExpandedViewHolder vh = new ExpandedViewHolder(v);
            return vh;
        } else if (viewType == HEADER_VIEW) {
            v = LayoutInflater.from(context).inflate(R.layout.list_item_header, parent, false);
            HeaderViewHolder vh = new HeaderViewHolder(v);
            return vh;
        } else {
            v = LayoutInflater.from(context).inflate(R.layout.list_item_grouped, parent, false);
            GroupedViewHolder vh = new GroupedViewHolder(v);
            return vh;
        }
    }

    // Now bind the ViewHolder in onBindViewHolder
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        try {
            if (holder instanceof ExpandedViewHolder) {
                ExpandedViewHolder vh = (ExpandedViewHolder) holder;
                vh.bindExpandedView(position);
            } else if (holder instanceof GroupedViewHolder) {
                GroupedViewHolder vh = (GroupedViewHolder) holder;
            } else if (holder instanceof HeaderViewHolder) {
                HeaderViewHolder vh = (HeaderViewHolder) holder;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Now the critical part. You have return the exact item count of your list
    // I've only one footer. So I returned data.size() + 1
    // If you've multiple headers and footers, you've to return total count
    // like, headers.size() + data.size() + footers.size()

    @Override
    public int getItemCount() {
        return DEMO_LIST_SIZE; // Let us consider we have 6 elements. This can be replaced with email chain size
    }

    // Now define getItemViewType of your own.
    @Override
    public int getItemViewType(int position) {
        if (positionTracker.get(position).equals(HEADER_VIEW)) {
            // This is where we'll add the header.
            return HEADER_VIEW;
        } else if (positionTracker.get(position).equals(GROUPED_VIEW)) {
            // This is where we'll add the header.
            return GROUPED_VIEW;
        } else if (positionTracker.get(position).equals(EXPANDED_VIEW)) {
            // This is where we'll add the header.
            return EXPANDED_VIEW;
        }

        return super.getItemViewType(position);
    }

    // So you're done with adding a footer and its action on onClick.
    // Now set the default ViewHolder for NormalViewHolder
    public class ViewHolder extends RecyclerView.ViewHolder {
        // Define elements of a row here
        public ViewHolder(View itemView) {
            super(itemView);
            // Find view by ID and initialize here
        }

        public void bindExpandedView(final int position) {
            // bindExpandedView() method to implement actions
            final WebView webView = itemView.findViewById(R.id.email_details_web_view);
            webView.getSettings().setBuiltInZoomControls(true);
            webView.getSettings().setDisplayZoomControls(false);
            webView.loadUrl("file:///android_asset/sample.html");
            webView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
                @Override
                public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    zoomListener.onZoomListener(position);
                }
            });
        }
    }
}

扩展的列表项包含一个WebView,它有一个包装器wrap_content。您将在list_item_expanded.xml 中找到以下布局。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <WebView
        android:id="@+id/email_details_web_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:scrollbars="none"
        tools:ignore="WebViewLayout" />
</RelativeLayout>

我尝试为实验添加一些虚拟数据,因此编写了Utility 类。 RecyclerView 设置为反向布局,因为这是在RecyclerView 中显示对话的普遍期望。

关键思想是scrollToPosition 在扩展WebView 时。这样感觉就像物品被向上和向下推动以适应扩展。希望你能明白。

我在这里添加了一些屏幕截图,让您了解到目前为止我可以实现的目标。

请注意,推送机制不顺畅。我将致力于此。但是,我想我应该把它贴在这里,因为这可能会帮助你思考。我想建议您clone the repository 并运行应用程序以检查整体实现。让我知道是否有任何反馈。

【讨论】:

  • 这似乎与 GMail 邮件详细信息视图几乎没有关系。当然有两个顶部工具栏;一个用于应用程序的粘性的,一个用于消息的可移动的。所有这些“未展开的消息”项从何而来?
  • @MartinZeitler 这只是分组消息的占位符。有时,多个电子邮件被链接和分组,由“消息未扩展”列表项表示。单击此按钮将展开可能具有相同捏缩放行为的消息。我刚刚添加了一个占位符,为您提供一个想法,即 Gmail 应用程序等多个视图项目是可能的。让我知道这是否能消除您的困惑。
【解决方案3】:

我尝试使用webview.setBuiltInZoomControls()ScaleGestureDetector 通过覆盖WebView 并将所有运动事件提供给检测器来实现此行为。它可以工作,但比例检测器和缩放的工作方式有点不同,而且用户体验很糟糕。

如果您仔细查看 Gmail 缩放实现和 webview 缩放,您会发现它们是不同的。我相信 Gmail 缩放是基于 view.setScaleX()view.setScaleY()。您可以通过继承WebView 并遵循this guide 来获得基本行为。您可能还需要致电view.setPivotX()view.setPivotY()。 Gmail 实现更复杂,因为它具有滚动功能,并且在您放大时似乎会向上滚动内容。您可以尝试使用一些实现可缩放容器并支持滚动的库,例如 this one。但是我无法使用WebView 使其正常工作。

总体而言,这是一项复杂的任务,您必须自己尝试实现以做出一些妥协并获得类似但不错的用户体验。

【讨论】:

    猜你喜欢
    • 2018-07-08
    • 1970-01-01
    • 1970-01-01
    • 2013-11-17
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-25
    相关资源
    最近更新 更多