如何实现Gmail像捏缩放和标题滚动

时间:2019-02-07 09:38:01

标签: android webview pinchzoom

如何实现类似Gmail应用程序的捏缩放行为?我已将标头容器放在ScrollView中,后跟WebView。似乎是非常复杂的行为。

此处没有缩放。

enter image description here

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

enter image description here

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

  <?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>

3 个答案:

答案 0 :(得分:3)

GMail使用启用了缩放缩放功能的Chrome WebView。缩放仅适用于单线程视图。 WebSettings setBuiltInZoomControls()默认为false,而setDisplayZoomControls()默认为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不仅限于水平抽屉;我认为这就是答案。

Google在自己的应用中大量使用(支持库)androidx类。假装这将是自定义实现,但这只是不了解给定标准行为的借口。

答案 1 :(得分:1)

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

I have created a GitHub repository,从中可以看到我的实施情况。我在那里也添加了一个自述文件来解释整个想法。

我尝试使用RecyclerView具有不同的ViewType来实现整个过程。我添加了如下适配器。

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。因此,感觉物品被向上和向下推以适应扩展。希望你能明白。

我将在此处添加一些屏幕截图,以使您了解到目前为止我可以实现的目标。

enter image description here

请注意,推动机构不平稳。我将为此工作。但是,我认为我应该在此处发布它,因为这可能会帮助您进行思考。我建议您clone the repository并运行该应用程序以检查整体实施情况。让我知道是否有任何反馈。

答案 2 :(得分:0)

我尝试通过覆盖webview.setBuiltInZoomControls()并将所有运动事件提供给检测器来使用ScaleGestureDetectorWebView来实现此行为。它可以工作,但是比例检测器和缩放的工作原理有所不同,UX变得很糟糕。

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

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