編輯:關於Android編程
首先帶大家看一下實現效果,用了兩種實現方式:
1.基於LinearLayout實現,導航欄不可響應手指滑動
2.基於HorizontalScrollView實現,導航欄可響應手指滑動
實現方式雖然不一樣,但是使用的是一樣的,因為我接口封裝的一模一樣,下面看實現效果。
基於LinearLayout的實現:
基於HorizontalScrollView的實現:
兩者效果一樣,區別就在於導航條可否隨用戶操作滑動。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPs/Cw+bWu8u1w/dMaW5lYXJMYXlvdXTKtc/Wo6xIb3Jpem9udGFsU2Nyb2xsVmlld732vfbKx8zXwcvSu7LjTGluZWFyTGF5b3V0o6zS8s6qy/zWu9TK0O3M19K7uPbX07/YvP6hozwvcD4NCjxwPtDox/O/qreiteOjujxiciAvPg0KMS7X1Lao0uW/2Lz+tcSx4NC0o6jNqNPDt723qKO6ubnU7Lqvyv2homluaXQoKaGib25NZWFzdXJloaJvbkxheW91dKGib25EcmF3oaJkaXNwYXRjaERyYXejqTxiciAvPg0KMi6w18mr1LK147XExqvSxsG/tcS8xsvjPGJyIC8+DQozLrWxVGFi0rOzrLn9o7W49rXEyrG68qOssNe147rNTGluZWFyTGF5b3V0xNrI3bXE1+m6z7ustq+jrLTvtb2w17Xjsru2r6OstvjOxNfW0sa2r7XE0Ke5+6GjPC9wPg0KPHA+yrXP1tXiyP2146Os1eK49r/YvP6+zcvjzerKwqOstbHIu9K70KnPuL3at73D5rXEo6y/ydLUuPm+3dfUvLq1xNDox/PIpbX308WhozwvcD4NCjxwPjxzdHJvbmc+MS7X1Lao0uW/2Lz+tcSx4NC0PC9zdHJvbmc+PGJyIC8+DQqjsSkg1eLA79b30qrKx29uTWVhc3VyZbe9t6i1xLi00LSjrLK7xNzTw8SsyM+1xG9uTWVhc3VyZaOs0OjSqs7Sw8e4+b7dVGFitcTK/cG/yKW8xsvjo6zPws28ysejsaOto7a49lRhYrXEbWVhc3VyZb3hufs8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160405/20160405094158445.png" title="\" />
這個就是根據Tab的數量平分屏幕的寬,當數量超過5個的時候,為了保持效果,只顯示5個,超出的內容將其追加到屏幕外面,屏幕是有界的,視圖是無界的,你可以無限往右邊添加,只要內存能容下,可以用個for循環無限添加看看發生什麼。
2) 復寫dispatchDraw方法,這裡dispatchDraw方法的作用就是畫一個白色的圓點。
白色圓點的偏移量的計算
這個得靠ViewPager的OnPageChangeListener,通過它onPageScrolled的回調去計算,計算好了調用invalidate()去重繪。
當Tab頁超過5個的時候,白點和LinearLayout內容的組合滑動,達到白點不動,而文字移動的效果。
當滑動到第5個Tab,如果只是白色圓點移動就會移動到屏幕外面去,這個時候也需要讓LinearLayout內容聯動,通過scrollTo方法,往與白點相反的方向移動內容,使其和白點的移動達到平衡,這樣白點不動,上面的標題內容移動。
下面分析,寫該控件需要的一些變量,先看截圖:
1.需要白點是吧,我們用畫筆畫,所以Paint需要
2.白點畫多大?所以需要一個白點的半徑變量radius
3.白點的偏移量,我們定義一個變量mOffset,根據ViewPager的滑動動態計算
4.上面還有一排文字,用TextView實現,這個TextView是根據Tab的數量動態添加的,數量不可控,我這裡並沒有定義成變量。但是文字內容對應的是一個Fragment的標題,也就是說標題和Fragment是一一對應的關系。OK用Map。
/**
* 存放Tab的集合 Key:導航欄的標題 Fragment:導航頁
*/
private Map mTabs;
TextView就是根據mTabs的數量動態添加的。
5.我們怎麼去計算滑動偏移量呢?這個得靠ViewPager的OnPageChangeListener,通過它onPageScrolled的回調去計算mOffset,就是通過這種計算圓點的偏移,這裡我持有了一個ViewPager的引用,所以有變量mViewPager,其實要不要關系不大.
6.屏幕最大可顯示的Tab數量MAX_VISIBLE_TAB_COUNTS,總不能有100個你就顯示100個吧.
7.實際屏幕上可顯示的Tab數量mRealVisibleTabCounts
8.屏幕寬度mScreenWidth輔助onMeasure計算的
下面是實現代碼:
package com.csm.hwtab;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* @author Rander.C
*/
@SuppressLint("NewApi")
public class TabLinearLayout extends LinearLayout implements OnClickListener, OnPageChangeListener {
/**
* 最大可顯示的Tab數,可寫成自定義屬性在xml裡面配置
*/
private static final int MAX_VISIBLE_TAB_COUNTS = 5;
/**
* 實際可見的Tab數,比如有8個Tab,超過8個,為了保證效果,最多顯示5個
* 此時mRealVisibleTabCounts為5
* 如果只有3個tab小於5個,則mRealVisibleTabCounts為3
* 並根據這個去計算每個tab的寬度,屏幕寬度除以mRealVisibleTabCounts嘛
*/
private int mRealVisibleTabCounts;
/**
* 小圓點滑動偏移量
*/
private float mOffset;
/**
* 繪制小圓點畫筆
*/
private Paint mPaint;
/**
* 小圓點半徑
*/
private int mCircleRadius;
/**
* 屏幕寬度
*/
private int mScreenWidth;
/**
* 存放Tab的集合 Key:導航欄的標題 Fragment:導航頁
*/
private Map mTabs;
/**
* View中有getContext()方法,用變量就會
* 多一個Context引用,會增加一丁點虛擬機的負擔嗎?
* 如果不用變量,但是每次都使用getContext(),是不是都要多一層
* 調用棧,這個怎麼權衡,求答案,還是我想多了?
*/
private Context mContext;
private ViewPager mViewPager;
public TabLinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public TabLinearLayout(Context context) {
this(context, null);
}
public TabLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mContext = getContext();
setOrientation(HORIZONTAL);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStyle(Style.FILL);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeCap(Cap.ROUND);
DisplayMetrics metrics = getResources().getDisplayMetrics();
mScreenWidth = metrics.widthPixels;
mCircleRadius = (int) getResources().getDimension(R.dimen.radius);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int tabCount = getChildCount();
if (tabCount == 0) {
return;
}
/** 不管默認測量結果怎麼樣,我們都要重新給其寬度賦值*/
mRealVisibleTabCounts = MAX_VISIBLE_TAB_COUNTS < tabCount ? MAX_VISIBLE_TAB_COUNTS : tabCount;
// 為每一個子view重新分配mScreenWidth / mRealVisibleTabCounts大小的寬度
int averageWidth = mScreenWidth / mRealVisibleTabCounts;
for (int i = 0; i < tabCount; i++) {
View view = getChildAt(i);
LinearLayout.LayoutParams params = (android.widget.LinearLayout.LayoutParams) view.getLayoutParams();
params.weight = 0;
params.width = averageWidth;
view.setLayoutParams(params);
}
}
/**
* 獲得Tabs列表
* @return
*/
public List getTabs() {
return new ArrayList(mTabs.values());
}
/**
* 添加一個Tab項,添加一個tab的時候同時給
* 該視圖添加一個TextView
* @param title
* @param tab
*/
public void addTab(String title, Fragment tab) {
if (mTabs == null) {
mTabs = new LinkedHashMap<>();
}
mTabs.put(title, tab);
addView(createTabItem(title, mTabs.size() - 1));
}
/**
* 設置ViewPager
* 同時給其設置OnPageChangeListener監聽器。
* @param viewPager
*/
public void setViewPager(ViewPager viewPager) {
mViewPager = viewPager;
mViewPager.setOnPageChangeListener(this);
}
/**
* 創建一個Tab視圖
* @param title Fragment對應的標題
* @param index 該Fragment對應的索引下標
* @return 已經構造好的tab視圖
*/
private View createTabItem(String title, int index) {
TextView tv_title = new TextView(mContext);
tv_title.setText(title);
tv_title.setTag(index);
tv_title.setGravity(Gravity.CENTER);
tv_title.setTextColor(Color.WHITE);
tv_title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
tv_title.setOnClickListener(this);
return tv_title;
}
@Override
public void onClick(View v) {
int tabIndex = (Integer) v.getTag();
if (onTabOperatorListener != null) {
onTabOperatorListener.onTabClick(tabIndex);
}
mViewPager.setCurrentItem(tabIndex);
}
@Override
public void onPageScrollStateChanged(int state) {
if (null != onTabOperatorListener) {
onTabOperatorListener.onPageScrollStateChanged(state);
}
}
@Override
public void onPageScrolled(int sourcePosition, float positionOffset, int positionOffsetPixels) {
mRealVisibleTabCounts = MAX_VISIBLE_TAB_COUNTS < getChildCount() ? MAX_VISIBLE_TAB_COUNTS : getChildCount();
int tabWidth = getWidth() / mRealVisibleTabCounts;
// 計算小圓點的偏移量,這個可以畫圖理解
mOffset = (int) (tabWidth / 2 + sourcePosition * tabWidth + positionOffset * tabWidth );
// 如果滾動到最後一個,則同時要向左滾動tab內容,這種情況是:原點在向右移動,同時整體又向做滑動,兩個滑動相互抵消,相當於原點沒有移動
if (getChildCount() > mRealVisibleTabCounts && positionOffset > 0
&& sourcePosition >= mRealVisibleTabCounts - 1) {
scrollTo((int) ((sourcePosition + 1 - mRealVisibleTabCounts) * tabWidth + tabWidth * positionOffset), 0);
}
invalidate();
if (null != onTabOperatorListener) {
onTabOperatorListener.onTabScrolled(sourcePosition, positionOffset, positionOffsetPixels);
}
}
@Override
public void onPageSelected(int index) {
if (null != onTabOperatorListener) {
onTabOperatorListener.onTabSelected(index);
}
/**
* 這裡不要多想,只是為了保證選中的tab為第一個時,讓布局重置到原點
*/
if(index == 0)
{
scrollTo(0, 0);
}
}
/**
* Tab操作相關的監聽器
* @author rander
*/
public interface OnTabOperatorListener {
void onTabClick(int tabIndex);
void onTabSelected(int tabIndex);
void onTabScrolled(int sourcePosition, float positionOffset, int positionOffsetPixels);
void onPageScrollStateChanged(int state);
}
public OnTabOperatorListener onTabOperatorListener;
public void setOnTabItemClickListener(OnTabOperatorListener onTabOperatorListener) {
this.onTabOperatorListener = onTabOperatorListener;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
//如果只有一個Tab則,不繪制圓點
if (getChildCount() <= 1) {
return;
}
//根據mOffset繪制偏移量
canvas.drawCircle(mOffset, getHeight() - mCircleRadius * 2, mCircleRadius, mPaint);
}
}
關於偏移量的計算畫圖理解:
偏移量計算的表達式:
mOffset = (int) (tabWidth / 2 + sourcePosition * tabWidth + positionOffset * tabWidth );
圖解:
第一個豎線和第二個豎線:tabWidth / 2
第二個豎線和第三個豎線:sourcePosition * tabWidth
第三個豎線和第四個豎線:positionOffset * tabWidth
一相加就是偏移量了,這個要找到sourcePosition和positionOffset的規律就好計算偏移量了,scrollTo的偏移量也可以參照這個理解。
接下來就是使用了.求Linux下的好的畫圖工具
書寫布局tab.xml:
在Activity中使用:
package com.csm.hwtab;
import com.csm.hwtab.adapter.MyFragmentPagerAdapter;
import com.csm.hwtab.fragment.OneFragment;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.view.Window;
public class TestActivity extends FragmentActivity{
private ViewPager mViewPager;
private TabLinearLayout mTabview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.tab);
initTabsView();
}
private void initTabsView() {
mViewPager = (ViewPager) findViewById(R.id.view_pager);
mTabview = (TabLinearLayout)findViewById(R.id.tab);
mTabview.addTab("先秦0", new OneFragment());
mTabview.addTab("先秦1", new OneFragment());
mTabview.addTab("先秦2", new OneFragment());
mTabview.addTab("先秦3", new OneFragment());
mTabview.addTab("先秦4", new OneFragment());
mTabview.addTab("先秦5", new OneFragment());
mTabview.addTab("先秦6", new OneFragment());
mViewPager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), mTabview.getTabs()));
mTabview.setViewPager(mViewPager);
mViewPager.setCurrentItem(0);
}
}
效果就如同上面的效果一樣了。
github:https://github.com/shuangmin/HwTab
1.回顧上篇學習 Android 布局 優化的知識 ,和 SeekBar (可拖動 滾動條)的理解與學習2.重點(1)RatingBar 的實現(2)OnRatingBa
這個錯誤翻譯的意思是:不能在沒有Looper.prepare的線程裡面創建handler。起初我很疑惑,我根本沒有用到工作線程,也沒有創建handler。報錯的代碼如下:
前言 用過微信的都知道,微信對話列表滑動刪除效果是很不錯的,這個效果我們也可以有。思路其實很簡單,弄個ListView,然後裡面的每個item做成一個可以滑動的
Android系統通過屬性暴露設備和運行時信息,並且可以通過設置屬性來控制系統行為。因此,屬性也像文件一樣,是一種需要保護的資源。在啟用SEAndroid之前,敏感屬性只