...

четверг, 5 декабря 2013 г.

Библиотека Header2ActionBar для Android





(демо для привлечения внимания)

Вы, наверное, уже видели похожее в приложениях от Google (Play Музыка, Google Пресса) и, возможно, каких-либо других. Для этих целей уже довольно давно существует библиотека от ManuelPeinadoFadingActionBar, которая прекрасно выполняет свою задачу, но к сожалению, имеет два «фатальных» недостатка.


Второй из них описан как известная проблема:



Known Issues


There is an important issue with the library and ListViews. More specifically, things don't work quite right when the activity is re-created due to a configuration change. So, unless you handle configuration changes yourself (or your activity is portrait/landscape only), I strongly suggest you stick to having your content in a ScrollView until a solution to this issue is found.



Стараясь исправить этот недостаток, я решил написать свою реализацию, тем самым устранив и оба недостатка :)






Библиотека состоит из трёх файлов:


FadingActionBarActivity.java


/**
* Created by AChep@xda <artemchep@gmail.com>
*/
public class FadingActionBarActivity extends Activity {

private static final String TAG = "FadingActionBarActivity";

private int mAlpha = 255;
private Drawable mDrawable;

private boolean isAlphaLocked;

public void setActionBarBackgroundDrawable(Drawable drawable) {
getActionBar().setBackgroundDrawable(drawable);
mDrawable = drawable;

if (mAlpha == 255) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
mAlpha = drawable.getAlpha();
} else {
setActionBarAlpha(mAlpha);
}
}

/**
* An {@link android.app.ActionBar} background drawable.
*
* @see #setActionBarBackgroundDrawable(android.graphics.drawable.Drawable)
* @see #setActionBarAlpha(int)
*/
public Drawable getActionBarBackgroundDrawable() {
return mDrawable;
}

/**
* Please use this method for global changes only!
* Otherwise, please, use {@link android.graphics.drawable.Drawable#setAlpha(int)}
* to {@link #getActionBarBackgroundDrawable()} directly.
*
* @param alpha a value from 0 to 255
* @see #getActionBarBackgroundDrawable()
* @see #getActionBarAlpha()
*/
public void setActionBarAlpha(int alpha) {
if (mDrawable == null) {
Log.w(TAG, "Set action bar background before setting alpha!");
return;
}
if (!isAlphaLocked) mDrawable.setAlpha(alpha);
mAlpha = alpha;
}

public int getActionBarAlpha() {
return mAlpha;
}

public void setActionBarAlphaLocked(boolean isLocked) {
isAlphaLocked = isLocked;
}

}



HeaderFragment .java


/**
* Little header fragment.
* <p>
* Created by AChep@xda <artemchep@gmail.com>
* </p>
*/
public class HeaderFragment extends Fragment {

private static final String TAG = "HeaderFragment";

private View mHeader;
private int mHeaderHeight;
private int mCurrentHeaderHeight;
private int mCurrentHeaderTranslateY;

private OnHeaderScrollChangeListener mOnHeaderScrollChangeListener;

public interface OnHeaderScrollChangeListener {
public void onHeaderScrollChanged(float progress, int height, int scroll);
}

public void setOnHeaderScrollChangeListener(OnHeaderScrollChangeListener listener) {
mOnHeaderScrollChangeListener = listener;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final Activity activity = getActivity();

mHeader = inflater.inflate(getHeaderResource(), container, false);
mHeaderHeight = mHeader.getLayoutParams().height;
mCurrentHeaderHeight = mHeaderHeight;
mCurrentHeaderTranslateY = 0;
onPrepareHeaderView(mHeader);

View content = inflater.inflate(getContentResource(), container, false);
assert content != null;
if (content instanceof ListView) {
final ListView listView = (ListView) content;

// Perform fake header view.
final Space listFakeHeader = new Space(activity);
listFakeHeader.setLayoutParams(new ListView.LayoutParams(
0, mHeaderHeight));

onPrepareContentListView(listView);
listView.addHeaderView(listFakeHeader);
listView.setOnScrollListener(new AbsListView.OnScrollListener() {

@Override
public void onScrollStateChanged(AbsListView absListView, int i) { /* unused */ }

@Override
public void onScroll(AbsListView absListView, int i, int i2, int i3) {
final View child = absListView.getChildAt(0);
if (child == listFakeHeader) {
updateHeaderScroll(child.getTop());
} else {
updateHeaderScroll(-mHeaderHeight);
}
}
});
} else {
onPrepareContentView(content);

final NotifyingScrollView scrollView = new NotifyingScrollView(activity);
scrollView.addView(content);
scrollView.setOnScrollChangedListener(new NotifyingScrollView.OnScrollChangedListener() {
@Override
public void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt) {
updateHeaderScroll(-t);
}
});
content = scrollView;
}

final FrameLayout root = new FrameLayout(activity);
root.addView(content, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
root.addView(mHeader);
return root;
}

private void updateHeaderScroll(int scrollTo) {
scrollTo = scrollTo > 0 ? 0 : scrollTo < -mHeaderHeight ? mHeaderHeight : scrollTo;

final boolean allowChangeHeight = isHeaderHeightFloating();
final int height = mHeaderHeight + scrollTo / 2;
final int transY = allowChangeHeight ? scrollTo / 2 : scrollTo;

if (height != mCurrentHeaderHeight && allowChangeHeight) {
final ViewGroup.LayoutParams lp = mHeader.getLayoutParams();
lp.height = height;
mHeader.setLayoutParams(lp);
mCurrentHeaderHeight = height;
}
if (transY != mCurrentHeaderTranslateY) {
mHeader.setTranslationY(transY);
mCurrentHeaderTranslateY = transY;

if (mOnHeaderScrollChangeListener != null) {
// Notify upper fragment to update ActionBar's alpha or whatever.
int scroll = Math.abs(scrollTo);
mOnHeaderScrollChangeListener.onHeaderScrollChanged(
(float) scroll / mHeaderHeight, mHeaderHeight, scroll);
}
}
}

/**
* If true, header's height might be changed on scroll.
* <p>Note: It takes a lot of calculations to measure the header all the time.</p>
*/
public boolean isHeaderHeightFloating() {
return false;
}

/**
* Int reference to header's resource.
*
* @see #onPrepareHeaderView(android.view.View)
* @see #getContentResource()
*/
public int getHeaderResource() {
return 0;
}

/**
* This is the place for setting up the header.
*
* @param view inflated header view.
* @see #getHeaderResource()
*/
public void onPrepareHeaderView(View view) { /* for my child */ }

/**
* Int reference to content's resource.
* <p>
* <b>Attention</b>: Parent view must be {@link android.widget.ListView ListView}
* or something else which will work inside of {@link android.widget.ScrollView ScrollView}.
* Otherwise it <b>WON'T</b> work.
* </p>
*
* @see #getHeaderResource()
* @see #onPrepareContentListView(ListView)
*/
public int getContentResource() {
return 0;
}

/**
* Called if the content's parent is a {@link android.widget.ListView ListView}.
*
* @see #getContentResource()
*/
public void onPrepareContentListView(ListView listView) { /* for my child */ }

/**
* Called if the content's parent is NOT a {@link android.widget.ListView ListView}.
*
* @see #getContentResource()
*/
public void onPrepareContentView(View view) { /* for my child */ }

}



NotifyingScrollView .java


/**
* @author Cyril Mottier with modifications from Manuel Peinado
*/
public class NotifyingScrollView extends ScrollView {
// Edge-effects don't mix well with the translucent action bar in Android 2.X
private boolean mDisableEdgeEffects = true;

/**
* @author Cyril Mottier
*/
public interface OnScrollChangedListener {
void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt);
}

private OnScrollChangedListener mOnScrollChangedListener;

public NotifyingScrollView(Context context) {
super(context);
}

public NotifyingScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public NotifyingScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mOnScrollChangedListener != null) {
mOnScrollChangedListener.onScrollChanged(this, l, t, oldl, oldt);
}
}

public void setOnScrollChangedListener(OnScrollChangedListener listener) {
mOnScrollChangedListener = listener;
}

@Override
protected float getTopFadingEdgeStrength() {
// http://stackoverflow.com/a/6894270/244576
if (mDisableEdgeEffects && Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return 0.0f;
}
return super.getTopFadingEdgeStrength();
}

@Override
protected float getBottomFadingEdgeStrength() {
// http://stackoverflow.com/a/6894270/244576
if (mDisableEdgeEffects && Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return 0.0f;
}
return super.getBottomFadingEdgeStrength();
}
}



и лежит на GitHub'е как проект библиотеки созданной в Android Studio.


Использование




HeaderFragment и FadingActionBarActivity наследуются от нативных собратьев, так что пока Android < 4.0 не поддерживается из коробки.

Наше приложение будет подобием демо на скриншоте сверху. Итак, пример Activity:




public class MainActivity extends FadingActionBarActivity {

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

// Инициализация фона ActionBar'a
setActionBarBackgroundDrawable(getResources().getDrawable(R.drawable.actionbar_bg));

FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, new TestHeaderFragment()
).commit();
}
}



public class TestHeaderFragment extends HeaderFragment {

@Override
public void onAttach(Activity activity) {
super.onAttach(activity);

// Меняем прозрачность ActionBar'a во время скроллинга
setOnHeaderScrollChangeListener(new OnHeaderScrollChangeListener() {
@Override
public void onHeaderScrollChanged(float progress, int height, int scroll) {
height -= getActivity().getActionBar().getHeight();
progress = (float) scroll / height;
if (progress > 1f) progress = 1f;
((FadingActionBarActivity) getActivity()).setActionBarAlpha((int) (255 * progress));
}
});
}

@Override
public int getHeaderResource() {
return R.layout.header;
}

@Override
public void onPrepareHeaderView(View view) {
super.onPrepareHeaderView(view);
// Заполняем view контентом
}

@Override
public int getContentResource() {
return R.layout.content;
}

@Override
public void onPrepareContentListView(ListView listView) {
super.onPrepareContentListView(listView);
// Заполняем view контентом
listView.setAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, android.R.id.title, new String[]{"Android", "Android", "Android", "Android", "Android", "Android", "Android", "Android", "Android", "Android", "Android"}));
}


Я заранее закрываю свое лицо руками и прошу прощения за свой код и английский. :(


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий