1. 程式人生 > >Android-導航欄特效-新聞類APP(仿iOS版網易新聞今日頭條的文字漸變縮放特效)

Android-導航欄特效-新聞類APP(仿iOS版網易新聞今日頭條的文字漸變縮放特效)

好久沒有寫文章了,慢慢的自己工作中遇到的問題不做積累,下次遇到還會忘。哎。。。。

週日無聊的單身程式設計師-唯有程式你懂的...
寫著程式聽著歌也是極好的!!

最近工作中要實現類似 今日頭條等新聞類APP頂部導航條的效果 ,不過我們這效果切換時要加上文字顏色的漸變和縮放。
git圖片比較大,耐心等待哦,小寶貝~~




一:分析

今天我們要實現這種特效。

用到的開源專案有:master-nineoldandroids-library.jar這個jar包,這個是向下相容的jar包,包括android一系列的動畫。

首先我們說一下這種 日頭條等新聞類APP 的基本實現是ViewPage+Fragment+HorizontalScrollView

我們今天說的就是這個HorizontalScrollView的特效

實現原理圖:

相信大家已經明白了大概

就是:最初時我們 初始化textview  並把選中的和正常的textview 初始化。就是上圖中的framelayout中的2個textview 放到集合 HashMap<String, View>()中。用於保持所有textview的狀態。

至於程式碼怎麼寫呢?!

android系統給我們提供了一個叫PagerSlidingTabStrip的類,在v4包中。我們把java中考出來放到我們的專案中,修改其中的程式碼就可以。

我們通過viewpage來控制導航條。把viewpage傳到PagerSlidingTabStrip中,並設定監聽器,程式碼如下:

public void setViewPager(ViewPager pager) {
		this.pager = pager;

		if (pager.getAdapter() == null) {
			throw new IllegalStateException(
					"ViewPager does not have adapter instance.");
		}

		pager.setOnPageChangeListener(pageListener);

		notifyDataSetChanged();
	}

二、獲取使用者切換時當前View和切換至的目的View。

ViewPager也需要監聽使用者的手勢,所以肯定提供了某個方法。於是縱觀ViewPager的方法,發現了一個叫做 onPageScrolled(int position, float positionOffset, int positionOffsetPixels)的方法~~

沒錯就是這個方法:在頁面滾動時呼叫~

下面仔細研究下這幾個引數:

直接說測試結果:

在非第一頁與最後一頁時,滑動到下一頁,position為當前頁位置;滑動到上一頁:position為當前頁-1

positionOffset 滑動到下一頁,[0,1)區間上變化;滑動到上一頁:(1,0]區間上變化

positionOffsetPixels這個和positionOffset很像:滑動到下一頁,[0,寬度)區間上變化;滑動到上一頁:(寬度,0]區間上變化

第一頁時:滑動到上一頁position=0 ,其他基本為0 ;最後一頁滑動到下一頁 position為當前頁位置,其他兩個引數為0

豁然發現,我們需要的步驟的第二步解決了,positionOffset很適合作為,漸變,縮放的控制引數;positionOffsetPixels則可以作為平移等的控制引數。

那麼如何獲得當前View和目的View呢:

分享幾個我的歧途:

1、【錯誤】我通過getChildAt(position),getChildAt(position+1),getChildAt(position-1)獲得滑動時,左右的兩個View;乍一看,還真覺得不錯~~~在程式碼寫出來,再乍效果也出不來~~錯誤原因:我們忽略一個特別大的東西,ViewPager的機制,滑動時動態載入和刪除View,ViewPager其實只會維持2到3個View,而position的範圍基本屬於無限~~

2、【錯誤】我通過getCurrentItem獲得當前的位置,然後+1,-1獲得後一個或者前一個~~正在竊喜,趕快程式碼改過來,效果怎麼也不對,亂七八糟的~~仔細觀察日誌,這個getCurrentItem當用戶手指離開的螢幕,Page還在動畫執行時,就改變了~~難怪~整個滑動過程並不是固定的~~唉,心都碎了~

3、【錯誤】position在整個滑動的過程中是不變化的,而且ViewPager會儲存2個或3個View;那麼我考慮,如果是第一頁、或者最後一頁那麼我取getChildAt(0)和getChildAt(1),如果在其他頁面則為getChildAt(0),getChildAt(2),然後經過一系列的變化~我想這會總該對了吧,於是我遇到第一問題,第一頁的時候,不管左右position都為0,尼瑪,這哪個為左View,哪個為右View~~

說了這麼多錯誤,大家可以繞過這些彎路,也能從這些彎路里面看出點什麼~

下面說正確的,其實ViewPager在新增一個View或者銷燬一個View時,是我們自己的PageAdapter中控制的,於是我們可以在ViewPager裡面維繫一個HashMap<Position,View>,然後滑動的時候,通過get(position)取出,比如上述效果,始終是右邊的View變化,要麼從小到大,要麼從大到小

那麼滑倒下一頁:左邊的View:map.get(position) ,右邊的View : map.get(position+1) . 

那麼滑倒上一頁:左邊的View : map.get(position) , 右邊的View : map.get(position+1) , 一樣的,因為滑到上一頁,position為當前頁-1


關鍵程式碼:

<pre name="code" class="java">	private class PageListener implements OnPageChangeListener {
		private int oldPosition = 0;

		@Override
		public void onPageScrolled(int position, float positionOffset,
				int positionOffsetPixels) {
			currentPosition = position;
			currentPositionOffset = positionOffset;

			scrollToChild(position, (int) (positionOffset * tabsContainer
					.getChildAt(position).getWidth()));

			invalidate();

			if (delegatePageListener != null) {
				delegatePageListener.onPageScrolled(position, positionOffset,
						positionOffsetPixels);
			}

			if (mState == State.IDLE && positionOffset > 0) {
				oldPage = pager.getCurrentItem();
				mState = position == oldPage ? State.GOING_RIGHT
						: State.GOING_LEFT;
			}
			boolean goingRight = position == oldPage;
			if (mState == State.GOING_RIGHT && !goingRight)
				mState = State.GOING_LEFT;
			else if (mState == State.GOING_LEFT && goingRight)
				mState = State.GOING_RIGHT;

			float effectOffset = isSmall(positionOffset) ? 0 : positionOffset;

			View mLeft = tabsContainer.getChildAt(position);
			View mRight = tabsContainer.getChildAt(position + 1);

			if (effectOffset == 0) {
				mState = State.IDLE;
			}

			if (mFadeEnabled)
				animateFadeScale(mLeft, mRight, effectOffset, position);

		}

		@Override
		public void onPageScrollStateChanged(int state) {
			if (state == ViewPager.SCROLL_STATE_IDLE) {
				scrollToChild(pager.getCurrentItem(), 0);
				mFadeEnabled = true;
			}

			if (delegatePageListener != null) {
				delegatePageListener.onPageScrollStateChanged(state);
			}
		}

		@Override
		public void onPageSelected(int position) {
			// selectedPosition = position;
			// updateTabStyles();
			currentPosition = position;

			// set old view statue
			ViewHelper.setAlpha(tabViews.get(oldPosition).get("normal"), 1);
			ViewHelper.setAlpha(tabViews.get(oldPosition).get("selected"), 0);
			View v_old = tabsContainer.getChildAt(oldPosition);
			ViewHelper.setPivotX(v_old, v_old.getMeasuredWidth() * 0.5f);
			ViewHelper.setPivotY(v_old, v_old.getMeasuredHeight() * 0.5f);
			ViewHelper.setScaleX(v_old, 1f);
			ViewHelper.setScaleY(v_old, 1f);

			// set new view statue
			ViewHelper.setAlpha(tabViews.get(position).get("normal"), 0);
			ViewHelper.setAlpha(tabViews.get(position).get("selected"), 1);
			View v_new = tabsContainer.getChildAt(position);
			ViewHelper.setPivotX(v_new, v_new.getMeasuredWidth() * 0.5f);
			ViewHelper.setPivotY(v_new, v_new.getMeasuredHeight() * 0.5f);
			ViewHelper.setScaleX(v_new, 1 + ZOOM_MAX);
			ViewHelper.setScaleY(v_new, 1 + ZOOM_MAX);

			if (delegatePageListener != null) {
				delegatePageListener.onPageSelected(position);
			}
			// oldPosition = selectedPosition;
			oldPosition = currentPosition;

		}

	}


可以看到程式碼:縮放view很關鍵:

View v_old = tabsContainer.getChildAt(oldPosition);
			ViewHelper.setPivotX(v_old, v_old.getMeasuredWidth() * 0.5f);
			ViewHelper.setPivotY(v_old, v_old.getMeasuredHeight() * 0.5f);
			ViewHelper.setScaleX(v_old, 1f);
			ViewHelper.setScaleY(v_old, 1f);
找到view的中心點即:setPivotX setPivotY 然後對X軸Y軸縮放。1為原始大小。>1放大,<1  且>0 縮小。

關鍵程式碼是:onPageScrolled 方法的底2個引數positionOffset 滑動的百分比。

漸變通過animateFadeScale這個方法控制:

	protected void animateFadeScale(View left, View right,
			float positionOffset, int position) {

		if (mState != State.IDLE) {
			if (left != null) {
				ViewHelper.setAlpha(tabViews.get(position).get("normal"),
						positionOffset);
				ViewHelper.setAlpha(tabViews.get(position).get("selected"),
						1 - positionOffset);

				float mScale = 1 + ZOOM_MAX - ZOOM_MAX * positionOffset;

				ViewHelper.setPivotX(left, left.getMeasuredWidth() * 0.5f);
				ViewHelper.setPivotY(left, left.getMeasuredHeight() * 0.5f);
				ViewHelper.setScaleX(left, mScale);
				ViewHelper.setScaleY(left, mScale);

			}
			if (right != null) {
				ViewHelper.setAlpha(tabViews.get(position + 1).get("normal"),
						1 - positionOffset);
				ViewHelper.setAlpha(tabViews.get(position + 1).get("selected"),
						positionOffset);

				float mScale = 1 + ZOOM_MAX * positionOffset;

				ViewHelper.setPivotX(right, right.getMeasuredWidth() * 0.5f);
				ViewHelper.setPivotY(right, right.getMeasuredHeight() * 0.5f);
				ViewHelper.setScaleX(right, mScale);
				ViewHelper.setScaleY(right, mScale);

			}
		}
	}


在你activity/引用的地方 中你可以直接設定TAB的顏色大小,正常色,選中色等。EG:

/**
	 * 對PagerSlidingTabStrip的各項屬性進行賦值。
	 */
	private void setTabsValue() {
		// 設定Tab是自動填充滿螢幕的
		tabs.setShouldExpand(true);
		// 設定Tab的分割線是透明的
		tabs.setDividerColor(Color.TRANSPARENT);
		// 設定Tab底部線的高度
		tabs.setUnderlineHeight((int) TypedValue.applyDimension(
				TypedValue.COMPLEX_UNIT_DIP, 1, dm));
		// 設定Tab Indicator的高度
		tabs.setIndicatorHeight((int) TypedValue.applyDimension(
				TypedValue.COMPLEX_UNIT_DIP, 4, dm));
		// 設定Tab標題文字的大小
		tabs.setTextSize((int) TypedValue.applyDimension(
				TypedValue.COMPLEX_UNIT_SP, 16, dm));
		// 設定Tab Indicator的顏色
		tabs.setIndicatorColor(Color.parseColor("#45c01a"));
		// 設定選中Tab文字的顏色 (這是我自定義的一個方法)
		tabs.setSelectedTextColor(Color.parseColor("#45c01a"));
		//設定正常Tab文字的顏色 (這是我自定義的一個方法)
		tabs.setTextColor(Color.parseColor("#C231C7"));
		// 取消點選Tab時的背景色
		tabs.setTabBackground(0);
	}


原始碼:

github地址:

注意:偷偷的告訴你最好去github地址哦,免費的,不要資源積

分。程式碼已經沒有多餘的類了。也可以直接食用依賴庫,github上都有說明哦!

不要忘記start哦!!

csdn資源-原始碼

原始碼