Android 仿今日頭條首頁標題欄效果
今天帶來的是仿今日頭條首頁的聯動滑動效果,廢話不多說,先上效果圖:
思路:
做這個我們需要實現的效果有
1、滑動內容區域,標題欄會有變化來顯示當前所處的位置。
2、點選標題欄,內容區域也會隨著滑動並跳到指定欄,標題欄會移動到螢幕中間。
3、當標題欄過多時,我們可以滑動標題欄找到超出螢幕的欄。
要實現上面三個問題,我採用的是標題欄用HorizontalScrollView
來實現,內容區域用ViewPager
來實現,力求達到最簡單的實現方式。
首先HorizontalScrollView
能夠水平方向拖動,只要在裡面包含一個水平方向的LinearLayout,然後LinearLayout裡面包含TextView,然後把HorizontalScrollView的HorizontalScrollBarEnabled設定為false,用於隱藏它原本的水平方向的滾動條,這樣就可以實現我們所說的第三點了。
再來看下第一點,要想標題欄隨著ViewPager變化,也就是我們得知道ViewPager當前所處的位置,這樣自然我們就想到了ViewPager的OnPageChangeListener方法:
@Override
public void onPageSelected(int position) {
}
其中的引數position為我們提供這個位置資訊。為了讓標題欄中的TextView和ViewPager中Fragement一一對應,由於TextView本身沒有這個屬性,所有我們可以通過重寫的方式給他新增一個index位置資訊。
現在主要的是第二個問題,這裡涉及到兩個滑動,ViewPager的滑動是自帶的我們可以不用管,而HorizontalScrollView本身是可以通過手動來滑動的,但是怎麼讓它自動滑到我們想要的地方呢。
還好HorizontalScrollView提供了一個smoothScrollTo(int x, int y)方法,使用這個方法,我們可以將HorizontalScrollView滑動到任意位置。
問題使我們怎麼確定這個位置,由於每個TextView裡面的文字數目可能不同,意味著TextView的寬度各不相同,這樣要怎麼計算位置呢?
我們可以使用getLeft()方法獲得目標TextView距離HorizontalScrollView最左邊的長度,這樣就不用管之前的TextView的寬度了,因為getLeft()相當於獲得了它們的和,我們希望它移動到中間位置,那麼getLeft()再減去(getWidth()-TextView.getWidth())/2就是需要滑動的距離,具體邏輯看圖:
圖中黑框是螢幕,紅色矩形是Textview,這裡要將Textview從A位置移動到B位置,其中的藍色線條就是移動的距離TextView.getLeft() - (getWidth() - TextView.getWidth()) / 2。getWidth()獲取的是螢幕寬度。
最後呼叫smoothScrollTo(int x, int y)方法就ok了,x傳上面得到的值,y為0就行。
最後給TextView加上屬性動畫效果就好了,對於屬性動畫ObjectAnimator
不太瞭解的自己去百度下吧,這裡就不介紹了。
既然問題都解決了下面貼上主要程式碼:
public class MyIndicator extends HorizontalScrollView implements ViewPager.OnPageChangeListener{
private ViewPager mViewPager;
private LinearLayout myLinearLayout;
/**
我們實現了ViewPager.OnPageChangeListener介面,因為我們要監聽ViewPager的滑頁行為,從而去改變導航欄的狀態
所以我們也可以看到,MyIndicator持有ViewPager的引用,如果我們還想要監聽ViewPager怎麼辦呢?
這裡我為MyIndicator提供了一個介面,與OnPageChangeListener方法一樣呼叫 **/
MyOnPageChangeListener mListener;
private interface MyOnPageChangeListener {
void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
void onPageSelected(int position);
void onPageScrollStateChanged(int state);
}
private int oldSelected;
//為了讓TextView能點選,我為每個TextView都設定了OnClickListener,
//就是獲得目標標題欄的index,然後呼叫setCurrentItem()就可以了,這樣就實現了點選滑動的效果,點選標題欄,Viewpager也會跟著翻頁。
private final OnClickListener mTabClickListener = new OnClickListener() {
public void onClick(View view) {
MyTabView tabView = (MyTabView)view;
oldSelected = mViewPager.getCurrentItem();
final int newSelected = tabView.index;
setCurrentItem(newSelected);
}
};
public MyIndicator(Context context) {
super(context);
init(context);
}
public MyIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyIndicator(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context mcontext){
setHorizontalScrollBarEnabled(false);//隱藏自帶的滾動條
//新增linearLayout
myLinearLayout = new LinearLayout(mcontext);
myLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
addView(myLinearLayout, new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
}
/**
* 我們必須在呼叫setViewPager()之前為ViewPager設定Adapter,
而FragmentPagerAdapter裡面的getCount()方法返回了這個數目,
如果沒有設定Adapter,MyIndicator就不知道怎麼繪製導航欄了,因為連標題數目都不清楚 */
public void setViewPager(ViewPager viewPager){
setViewPager(viewPager,0);
}
@SuppressWarnings("deprecation")
public void setViewPager(ViewPager viewPager,int initPos){
if (mViewPager == viewPager) {
return;
}
if (mViewPager != null) {
mViewPager.setOnPageChangeListener(null);
}
final PagerAdapter adapter = viewPager.getAdapter();
if (adapter == null) {
throw new IllegalStateException("ViewPager does not have adapter instance.");
}
mViewPager = viewPager;
viewPager.setOnPageChangeListener(this);
notifyDataSetChanged();
setCurrentItem(initPos);
}
private void notifyDataSetChanged(){
myLinearLayout.removeAllViews();
PagerAdapter mAdapter = mViewPager.getAdapter();
int count = mAdapter.getCount();
for(int i=0;i<count;i++){
addTab(i,mAdapter.getPageTitle(i));
}
requestLayout();
}
/**
* 程式碼新增頂部的TextView
* @param index
* @param text
*/
private void addTab(int index,CharSequence text) {
MyTabView tabView = new MyTabView(getContext());
tabView.index = index;
tabView.setFocusable(true);
tabView.setOnClickListener(mTabClickListener);
tabView.setText(text);
tabView.setTextSize(22);
tabView.setTextColor(getContext().getResources().getColor(R.color.white));
tabView.setPadding(20,0,20,0);
myLinearLayout.addView(tabView);
}
/**
* 被選中的動畫
* @param view
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@SuppressLint("NewApi")
private void animation(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f);
ObjectAnimator fade = ObjectAnimator.ofFloat(view, "alpha", 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleX).with(scaleY).with(fade);
animSet.setDuration(500);
animSet.start();
}
/**
* 沒選中的動畫
* @param view
*/
private void animation2(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 0.8f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 0.8f);
ObjectAnimator fade = ObjectAnimator.ofFloat(view, "alpha", 0.5f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(scaleX).with(scaleY).with(fade);
animSet.setDuration(500);
animSet.start();
}
public void setCurrentItem(int item) {
if (mViewPager == null) {
throw new IllegalStateException("ViewPager has not been bound.");
}
int mSelectedTabIndex = item;
mViewPager.setCurrentItem(item);
final int tabCount = myLinearLayout.getChildCount();
for (int i = 0; i < tabCount; i++) {//遍歷標題,改變選中的背景
final View child = myLinearLayout.getChildAt(i);
final boolean isSelected = (i == item);
child.setSelected(isSelected);
if (isSelected) {
animation(child);
animateToTab(item);//動畫效果
}else{
animation2(child);
child.setBackgroundColor(Color.TRANSPARENT);
}
}
}
private Runnable mTabSelector;
private void animateToTab(final int position) {
final View tabView = myLinearLayout.getChildAt(position);
if (mTabSelector != null) {
removeCallbacks(mTabSelector);
}
mTabSelector = new Runnable() {
public void run() {
final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;//計算要滑動到的位置
smoothScrollTo(scrollPos, 0);
mTabSelector = null;
}
};
post(mTabSelector); //在主執行緒執行動畫
}
public void setMyOnPageChangeListener(MyOnPageChangeListener listener){
mListener = listener;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if(mListener!=null) mListener.onPageScrolled(position,positionOffset,positionOffsetPixels);
}
@Override
public void onPageSelected(int position) {
setCurrentItem(position);
if(mListener!=null) mListener.onPageSelected(position);
}
@Override
public void onPageScrollStateChanged(int state) {
if(mListener!=null) mListener.onPageScrollStateChanged(state);
}
}
因為裡面註釋都寫的差不多了,這裡就不再細說了。
下面是佈局xml檔案:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.zhu.myindicator.MainActivity" >
<com.zhu.myindicator.MyIndicator
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/red" />
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
最後在Activity裡面呼叫:
public class MainActivity extends FragmentActivity {
private MyIndicator indicator;
private ViewPager pager;
String content[]={"熱點","推薦","社會","視訊","科技","汽車","趣圖", "美圖", "美女"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentPagerAdapter adapter = new MyFragmentManager(getSupportFragmentManager(), content);
pager = (ViewPager)findViewById(R.id.pager);
indicator = (MyIndicator)findViewById(R.id.indicator);
pager.setAdapter(adapter);
indicator.setViewPager(pager);
}
}