web-dev-qa-db-ja.com

Lollipopツールバーアニメーションの展開/折りたたみ(Telegramアプリ)

ツールバーの展開/折りたたみアニメーションがどのように行われるかを把握しようとしています。 Telegramアプリの設定を見ると、リストビューとツールバーがあることがわかります。下にスクロールするとツールバーが折りたたまれ、上にスクロールすると展開されます。プロファイル写真とFABのアニメーションもあります。誰にもそれについての手がかりがありますか?彼らはその上にすべてのアニメーションを構築したと思いますか?たぶん、新しいAPIやサポートライブラリから何かが欠けているのかもしれません。

Googleカレンダーアプリでも、スピナーを開くと同じ動作に気付きました(スピナーではないと思いますが、見た目は似ています)。ツールバーが拡大し、上にスクロールすると折りたたまれます。

明確にするために、QuickReturnメソッドは必要ありません。おそらく、Telegramアプリが類似のものを使用していることを知っています。必要な正確な方法は、Googleカレンダーアプリの効果です。私が試した

Android:animateLayoutChanges="true"

そして、エキスパンド方法はかなりうまく機能します。しかし、明らかに、ListViewを上にスクロールしても、ツールバーは折りたたまれません。

GestureListenerを追加することも考えましたが、これを達成するためのAPIまたはより簡単な方法があるかどうかを知りたいです。

ない場合は、GestureListenerを使用すると思います。うまくいけば、アニメーションの効果がスムーズになります。

ありがとう!

72
edoardotognoni

編集:

Android Designサポートライブラリのリリース以来、より簡単な解決策があります。チェック joaquin's answer

-

ここに私がそれをした方法があります、おそらく他の多くの解決策がありますが、これは私のために働いた。

  1. まず、透明な背景を持つToolbarを使用する必要があります。展開と折りたたみToolbarは、実際には、透明なToolbarの下にあるfakeの1つです。 (下の最初のスクリーンショット-マージンのあるスクリーンショット-で、これがTelegramでも同じであることがわかります)。

    ToolbarとオーバーフローNavigationIconの実際のMenuItemのみを保持します。

    1. Transparent Toolbar - 2. Expanded header - 3. Collapsed header

  2. 2番目のスクリーンショットの赤い長方形にあるものすべて(つまり、偽のToolbarFloatingActionButton)は、実際にはヘッダー設定に追加しますListView(またはScrollView)。

    したがって、次のような別のファイルにこのヘッダーのレイアウトを作成する必要があります。

     <!-- The headerView layout. Includes the fake Toolbar & the FloatingActionButton -->
    
     <FrameLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">
    
        <RelativeLayout
            Android:id="@+id/header_container"
            Android:layout_width="match_parent"
            Android:layout_height="@dimen/header_height"
            Android:layout_marginBottom="3dp"
            Android:background="@Android:color/holo_blue_dark">
    
            <RelativeLayout
                Android:id="@+id/header_infos_container"
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:layout_alignParentBottom="true"
                Android:padding="16dp">
    
                <ImageView
                    Android:id="@+id/header_picture"
                    Android:layout_width="wrap_content"
                    Android:layout_height="wrap_content"
                    Android:layout_centerVertical="true"
                    Android:layout_marginRight="8dp"
                    Android:src="@Android:drawable/ic_dialog_info" />
    
                <TextView
                    Android:id="@+id/header_title"
                    style="@style/TextAppearance.AppCompat.Title"
                    Android:layout_width="wrap_content"
                    Android:layout_height="wrap_content"
                    Android:layout_toRightOf="@+id/header_picture"
                    Android:text="Toolbar Title"
                    Android:textColor="@Android:color/white" />
    
                <TextView
                    Android:id="@+id/header_subtitle"
                    style="@style/TextAppearance.AppCompat.Subhead"
                    Android:layout_width="wrap_content"
                    Android:layout_height="wrap_content"
                    Android:layout_below="@+id/header_title"
                    Android:layout_toRightOf="@+id/header_picture"
                    Android:text="Toolbar Subtitle"
                    Android:textColor="@Android:color/white" />
    
            </RelativeLayout>
        </RelativeLayout>
    
        <FloatingActionButton
            Android:id="@+id/header_fab"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:layout_gravity="bottom|right"
            Android:layout_margin="10dp"
            Android:src="@drawable/ic_open_in_browser"/>
    
    </FrameLayout>
    

    (2 Viewsにまたがるファブに対して負のマージン/パディングを使用できることに注意してください)

  3. ここからが興味深い部分です。偽のToolbarの拡張をアニメーション化するために、ListViewonScrollListenerを実装します。

    // The height of your fully expanded header view (same than in the xml layout)
    int headerHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
    // The height of your fully collapsed header view. Actually the Toolbar height (56dp)
    int minHeaderHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height);
    // The left margin of the Toolbar title (according to specs, 72dp)
    int toolbarTitleLeftMargin = getResources().getDimensionPixelSize(R.dimen.toolbar_left_margin);
    // Added after edit
    int minHeaderTranslation;
    
    private ListView listView;
    
    // Header views
    private View headerView;
    private RelativeLayout headerContainer;
    private TextView headerTitle;
    private TextView headerSubtitle;
    private FloatingActionButton headerFab;
    
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View rootView = inflater.inflate(R.layout.listview_fragment, container, false);
        listView = rootView.findViewById(R.id.listview);
    
        // Init the headerHeight and minHeaderTranslation values
    
        headerHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
        minHeaderTranslation = -headerHeight + 
            getResources().getDimensionPixelOffset(R.dimen.action_bar_height);
    
        // Inflate your header view
        headerView = inflater.inflate(R.layout.header_view, listview, false);
    
        // Retrieve the header views
        headerContainer = (RelativeLayout) headerView.findViewById(R.id.header_container);
        headerTitle = (TextView) headerView.findViewById(R.id.header_title);
        headerSubtitle = (TextView) headerView.findViewById(R.id.header_subtitle);
        headerFab = (TextView) headerView.findViewById(R.id.header_fab);;
    
        // Add the headerView to your listView
        listView.addHeaderView(headerView, null, false);
    
        // Set the onScrollListener
        listView.setOnScrollListener(this);        
    
        // ...
    
        return rootView;
    }
    
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState)
    {
        // Do nothing
    }
    
    
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
    {
        Integer scrollY = getScrollY(view);
    
        // This will collapse the header when scrolling, until its height reaches
        // the toolbar height
        headerView.setTranslationY(Math.max(0, scrollY + minHeaderTranslation));
    
        // Scroll ratio (0 <= ratio <= 1). 
        // The ratio value is 0 when the header is completely expanded, 
        // 1 when it is completely collapsed
        float offset = 1 - Math.max(
            (float) (-minHeaderTranslation - scrollY) / -minHeaderTranslation, 0f);
    
    
        // Now that we have this ratio, we only have to apply translations, scales,
        // alpha, etc. to the header views
    
        // For instance, this will move the toolbar title & subtitle on the X axis 
        // from its original position when the ListView will be completely scrolled
        // down, to the Toolbar title position when it will be scrolled up.
        headerTitle.setTranslationX(toolbarTitleLeftMargin * offset);
        headerSubtitle.setTranslationX(toolbarTitleLeftMargin * offset);
    
        // Or we can make the FAB disappear when the ListView is scrolled 
        headerFab.setAlpha(1 - offset);
    }
    
    
    // Method that allows us to get the scroll Y position of the ListView
    public int getScrollY(AbsListView view)
    {
        View c = view.getChildAt(0);
    
        if (c == null)
            return 0;
    
        int firstVisiblePosition = view.getFirstVisiblePosition();
        int top = c.getTop();
    
        int headerHeight = 0;
        if (firstVisiblePosition >= 1)
            headerHeight = this.headerHeight;
    
        return -top + firstVisiblePosition * c.getHeight() + headerHeight;
    }
    

このコードにはテストしなかった部分があるので、間違いをハイライトしてください。しかし、全体的には、改善できると確信しているにもかかわらず、このソリューションが機能することはわかっています。

編集2:

上記のコードにいくつかの間違いがありました(今日までテストしなかった...)ので、それを機能させるために数行を変更しました:

  1. MinHeaderHeightに代わる別の変数minHeaderTranslationを導入しました。
  2. ヘッダービューに適用されるY変換値を変更しました:

        headerView.setTranslationY(Math.max(-scrollY, minHeaderTranslation));
    

    に:

        headerView.setTranslationY(Math.max(0, scrollY + minHeaderTranslation));
    

    前の表現はまったく機能していませんでした、ごめんなさい...

  3. 比率の計算も変更されたため、(画面の上部ではなく)ツールバーの下部から完全に展開されたヘッダーに進化します。

110
MathieuMaree

また、AndroidチームでChris Banesによって書かれたCollapsingTitleLayoutもチェックしてください。 https://plus.google.com/+ChrisBanes/posts/J9Fwbc15BHN

enter image description here

コード: https://Gist.github.com/chrisbanes/91ac8a20acfbdc410a68

26
hidro

設計サポートライブラリを使用 http://Android-developers.blogspot.in/2015/05/Android-design-support-library.html

これをbuild.gradleに含めます

compile 'com.Android.support:design:22.2.0'    
compile 'com.Android.support:appcompat-v7:22.2.+'

リサイクル業者の見解については、これも含める

compile 'com.Android.support:recyclerview-v7:22.2.0' 
    <!-- AppBarLayout allows your Toolbar and other views (such as tabs provided by TabLayout) 
    to react to scroll events in a sibling view marked with a ScrollingViewBehavior.-->
    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/appbar"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:fitsSystemWindows="true">

        <!-- specify tag app:layout_scrollFlags -->
        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:layout_width="match_parent"
            Android:layout_height="?attr/actionBarSize"
            Android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"/>

        <!-- specify tag app:layout_scrollFlags -->
        <Android.support.design.widget.TabLayout
            Android:id="@+id/tabLayout"
            Android:scrollbars="horizontal"
            Android:layout_below="@+id/toolbar"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"/>

        <!--  app:layout_collapseMode="pin" will help to pin this view at top when scroll -->
        <TextView
            Android:layout_width="match_parent"
            Android:layout_height="50dp"
            Android:text="Title"
            Android:gravity="center"
            app:layout_collapseMode="pin" />

    </Android.support.design.widget.AppBarLayout>

    <!-- This will be your scrolling view. 
    app:layout_behavior="@string/appbar_scrolling_view_behavior" tag connects this features -->
    <Android.support.v7.widget.RecyclerView
        Android:id="@+id/list"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent">

    </Android.support.v7.widget.RecyclerView>

</Android.support.design.widget.CoordinatorLayout>

アクティビティを拡張する必要がありますAppCompatActivity

public class YourActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.your_layout);

        //set toolbar
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }

}

アプリのテーマは次のようになります

    <resources>
            <!-- Base application theme. -->   
            <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
            </style>
    </resources>
8
deniz

これは私の実装です:

collapsedHeaderHeightexpandedHeaderHeightはどこかで定義されており、関数getAnimationProgressで展開/折りたたみの進行状況を取得できます。この値に基づいてアニメーションを実行し、実際のヘッダーを表示/非表示します。

  listForumPosts.setOnScrollListener(new AbsListView.OnScrollListener() {

        /**
         * @return [0,1], 0 means header expanded, 1 means header collapsed
         */
        private float getAnimationProgress(AbsListView view, int firstVisibleItem) {
            if (firstVisibleItem > 0)
                return 1;

            // should not exceed 1
            return Math.min(
                    -view.getChildAt(0).getTop() / (float) (expandedHeaderHeight - collapsedHeaderHeight), 1);
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            // at render beginning, the view could be empty!
            if (view.getChildCount() > 0) {
                float animationProgress = getAnimationProgress(view, firstVisibleItem);
                imgForumHeaderAvatar.setAlpha(1-animationProgress);
                if (animationProgress == 1) {
                    layoutForumHeader.setVisibility(View.VISIBLE);
                } else {
                    layoutForumHeader.setVisibility(View.GONE);
                }
            }
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // do nothing
        }

    }
1
cn123h