Java tutorial
/* * Copyright (C) 2013 AChep@xda <artemchep@gmail.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.achep.header2actionbar; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.AbsListView; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.ScrollView; import android.widget.Space; /** * Little header fragment. * <p> * <b>Important</b>: Use {@link android.R.id#background} to specify background view and * {@link android.R.id#title} to specify view on top of the header * (for example: a shadow for {@code ActionBar}). * <p> * Created by AChep@xda <artemchep@gmail.com> */ // let's \'/ // remember -= * =- // happy {.} // 2013 {.-'} // year! {`_.-'} // It was {-` _.-'} // amazing! `":=:"` // `---` public abstract class HeaderFragmentSupportV4 extends Fragment { private static final String TAG = "HeaderFragmentSupportV4"; public static final int HEADER_BACKGROUND_SCROLL_NORMAL = 0; public static final int HEADER_BACKGROUND_SCROLL_PARALLAX = 1; public static final int HEADER_BACKGROUND_SCROLL_STATIC = 2; private FrameLayout mFrameLayout; private View mContentOverlay; // header private View mHeader; private View mHeaderHeader; private View mHeaderBackground; private View mContentView; private ScrollView mContentWrapper; private int mHeaderHeight; private int mHeaderScroll; private int mHeaderBackgroundScrollMode = HEADER_BACKGROUND_SCROLL_NORMAL; private Space mFakeHeader; private boolean isListViewEmpty; // listeners private AbsListView.OnScrollListener mOnScrollListener; private OnHeaderScrollChangedListener mOnHeaderScrollChangedListener; public interface OnHeaderScrollChangedListener { public void onHeaderScrollChanged(float progress, int height, int scroll); } public void setOnHeaderScrollChangedListener(OnHeaderScrollChangedListener listener) { mOnHeaderScrollChangedListener = listener; } public void setHeaderBackgroundScrollMode(int scrollMode) { mHeaderBackgroundScrollMode = scrollMode; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final Activity activity = getActivity(); assert activity != null; mFrameLayout = new FrameLayout(activity); mHeader = onCreateHeaderView(inflater, mFrameLayout); mHeaderHeader = mHeader.findViewById(android.R.id.title); mHeaderBackground = mHeader.findViewById(android.R.id.background); assert mHeader.getLayoutParams() != null; mHeader.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); mHeaderHeight = mHeader.getMeasuredHeight(); mFakeHeader = new Space(activity); View content = onCreateContentView(inflater, mFrameLayout); mContentView = content; Log.i(TAG, "container:" + container.getMeasuredHeight() + ",mHeaderHeight=" + mHeaderHeight); final View topContentView = container; if (content instanceof ListView) { isListViewEmpty = true; final ListView listView = (ListView) content; mFakeHeader.setLayoutParams(new ListView.LayoutParams(0, mHeaderHeight)); listView.addHeaderView(mFakeHeader); listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView absListView, int scrollState) { if (mOnScrollListener != null) { mOnScrollListener.onScrollStateChanged(absListView, scrollState); } } @Override public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (isListViewEmpty) { scrollHeaderTo(0); } else { final View child = absListView.getChildAt(0); assert child != null; scrollHeaderTo(child == mFakeHeader ? child.getTop() : -mHeaderHeight); } } }); } else { topContentView.getViewTreeObserver() .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @SuppressWarnings("deprecation") @Override public void onGlobalLayout() { //Remove it here unless you want to get this callback for EVERY //layout pass, which can get you into infinite loops if you ever //modify the layout from within this method. topContentView.getViewTreeObserver().removeGlobalOnLayoutListener(this); //Now you can get the width and height from content int actionBarHeight = 0; TypedValue tv = new TypedValue(); if (getActivity().getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) { actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics()); } Log.i(TAG, "topContentView:" + topContentView.getHeight() + ", actionBarHeight:" + actionBarHeight + ", getStatusBarHeight:" + getStatusBarHeight()); ViewGroup.LayoutParams lp = mContentView.getLayoutParams(); lp.height = topContentView.getHeight() - actionBarHeight - getStatusBarHeight(); mContentView.setLayoutParams(lp); } }); // Merge fake header view and content view. final LinearLayout view = new LinearLayout(activity); view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); view.setOrientation(LinearLayout.VERTICAL); mFakeHeader.setLayoutParams(new LinearLayout.LayoutParams(0, mHeaderHeight)); view.addView(mFakeHeader); view.addView(content); // Put merged content to ScrollView final NotifyingScrollView scrollView = new NotifyingScrollView(activity); scrollView.addView(view); scrollView.setVerticalScrollBarEnabled(false); scrollView.setOverScrollMode(ScrollView.OVER_SCROLL_NEVER); scrollView.setOnScrollChangedListener(new NotifyingScrollView.OnScrollChangedListener() { @Override public void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt) { Log.i(TAG, "onScrollChanged scroll ot -t :" + (-t)); scrollHeaderTo(-t); } }); mContentWrapper = scrollView; content = scrollView; } mFrameLayout.addView(content); mFrameLayout.addView(mHeader); // Content overlay view always shows at the top of content. if ((mContentOverlay = onCreateContentOverlayView(inflater, mFrameLayout)) != null) { mFrameLayout.addView(mContentOverlay, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } // Post initial scroll mFrameLayout.post(new Runnable() { @Override public void run() { Log.i(TAG, "post initial scroll to 0"); // SEAN: walk around for scroll position bug if (mContentWrapper != null) { mContentWrapper.scrollTo(0, 0); } scrollHeaderTo(0, true); } }); return mFrameLayout; } private void scrollHeaderTo(int scrollTo) { scrollHeaderTo(scrollTo, false); } private void scrollHeaderTo(int scrollTo, boolean forceChange) { Log.i(TAG, "scrollTo:" + scrollTo + ",forceChange:" + forceChange); scrollTo = Math.min(Math.max(scrollTo, -mHeaderHeight), 0); if (mHeaderScroll == (mHeaderScroll = scrollTo) & !forceChange) return; setViewTranslationY(mHeader, scrollTo); setViewTranslationY(mHeaderHeader, scrollTo); switch (mHeaderBackgroundScrollMode) { case HEADER_BACKGROUND_SCROLL_NORMAL: setViewTranslationY(mHeaderBackground, 0); break; case HEADER_BACKGROUND_SCROLL_PARALLAX: setViewTranslationY(mHeaderBackground, -scrollTo / 1.6f); break; case HEADER_BACKGROUND_SCROLL_STATIC: setViewTranslationY(mHeaderBackground, -scrollTo); break; } if (mContentOverlay != null) { final ViewGroup.LayoutParams lp = mContentOverlay.getLayoutParams(); final int delta = mHeaderHeight + scrollTo; lp.height = mFrameLayout.getHeight() - delta; mContentOverlay.setLayoutParams(lp); mContentOverlay.setTranslationY(delta); } notifyOnHeaderScrollChangeListener((float) -scrollTo / mHeaderHeight, mHeaderHeight, -scrollTo); } private void setViewTranslationY(View view, float translationY) { if (view != null) view.setTranslationY(translationY); } private void notifyOnHeaderScrollChangeListener(float progress, int height, int scroll) { if (mOnHeaderScrollChangedListener != null) { mOnHeaderScrollChangedListener.onHeaderScrollChanged(progress, height, scroll); } } public abstract View onCreateHeaderView(LayoutInflater inflater, ViewGroup container); public abstract View onCreateContentView(LayoutInflater inflater, ViewGroup container); public abstract View onCreateContentOverlayView(LayoutInflater inflater, ViewGroup container); public void setListViewAdapter(ListView listView, ListAdapter adapter) { isListViewEmpty = adapter == null; listView.setAdapter(null); listView.removeHeaderView(mFakeHeader); listView.addHeaderView(mFakeHeader); listView.setAdapter(adapter); } /** * {@inheritDoc AbsListView#setOnScrollChangedListener} */ public void setListViewOnScrollChangedListener(AbsListView.OnScrollListener listener) { mOnScrollListener = listener; } // ////////////////////////////////////////// // //////////// -- GETTERS -- /////////////// // ////////////////////////////////////////// public View getHeaderView() { return mHeader; } public View getHeaderHeaderView() { return mHeaderHeader; } public View getHeaderBackgroundView() { return mHeaderBackground; } public int getHeaderBackgroundScrollMode() { return mHeaderBackgroundScrollMode; } public int getStatusBarHeight() { int result = 0; int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = getResources().getDimensionPixelSize(resourceId); } return result; } }