自定義仿QQ主介面選項卡
自定義QQ主介面選項卡
QQ Android版本的效果先貼上來
可以看到這個可愛的選項卡,其實使用xml佈局可以很容易的弄出來,但是博主就帶大家封裝成一個自定義控制元件!
博主實現的效果
這速度。。。抱歉哈,博主也不知道為啥這麼快。。。。
可以看到,支援的還是挺豐富的,還支援包裹,根據自定義屬性tabWidht來計算寬度
其實實現起來很簡單,下面博主就帶小白們來實現一下,大牛請忽略
分析
問題
實現上述的效果,如果我們是繼承View,那麼裡面的文字、內邊框、圓角效果都要自己繪製出來
而且還要支援字型大小的改變和裡面文字的排列,顯然這樣子的代價太大
在我們平常的xml佈局中,如果遇到類似的效果,我們很容易就可以想到線性佈局
然後裡面放幾個文字控制元件並且使用權重進行等分寬度,所以今天的實現的思路就是這樣的,只不過平時我們在xml手寫的程式碼都封裝起來!
總體思路
1.自定義控制元件繼承LinearLayout,設定本身為水平
2.讀取自定義屬性到類中
3.根據所有自定義的屬性往LinearLayout中新增TextView控制元件
4.最後就是被系統給繪製顯示出來了(這步沒我們的事..系統做)
額外的
1.圓角我們使用背景來實現就可以了,GradientDrawable完全可以勝任
2.新增TextView的點選事件,改變選中的下標,然後呼叫上述的第三步!
3.提供介面給使用者監聽被選中的下標和文字
完工
首先國際慣例,決定繼承的父類
三個引數的建構函式中就是我們上面說的幾個步驟啦
支援的屬性及其預設的值
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="XTabHost">
<!--半徑-->
<attr name="radius" format="dimension" />
<!--文字大小-->
<attr name="text_size" format="dimension" />
<!--文字選中的顏色-->
<attr name="text_select_color" format="color" />
<!--文字未選中的顏色-->
<attr name="text_unselect_color" format="color" />
<!--tab選中的顏色-->
<attr name="tab_select_color" format="color" />
<!--tab沒選中的顏色-->
<attr name="tab_unselect_color" format="color" />
<!--tab間距-->
<attr name="tab_space" format="dimension" />
<!--tab的寬,在包裹的時候用到-->
<attr name="tab_width" format="dimension"/>
<!--tab的高,在包裹的時候用到-->
<attr name="tab_height" format="dimension"/>
<!--整體的背景-->
<attr name="bg" format="color" />
<!--預設顯示第幾個-->
<attr name="default_index" format="integer" />
<!--顯示的文字陣列-->
<attr name="src" format="reference" />
</declare-styleable>
</resources>
對應到類中
/**
* 自身控制元件的背景
*/
private int backBg = Color.WHITE;
/**
* 沒有選中的tab的背景
*/
private int unSelectTabBg = Color.BLUE;
/**
* 選中的tab的背景
*/
private int selectTabBg = Color.WHITE;
/**
* 一個Tab的寬和高,在自身是包裹的時候會被用到
* -1表示不起作用,計算的時候按照包裹孩子處理
* 80是dp的單位
*/
private int tabWidth = 80, tabHeight = -1;
/**
* 未選中的文字的顏色
*/
private int unSelectTextColor = Color.WHITE;
/**
* 選中的文字的顏色
*/
private int selectTextColor = Color.BLUE;
/**
* 預設的字型大小,sp
*/
private int textSize = 16;
/**
* 間距,px
*/
private int space = 1;
/**
* 圓角半徑,dp
*/
private int radius = 0;
/**
* 當前的下標
*/
private int curIndex = 1;
/**
* 所有要顯示的文字
*/
private String[] textArr = new String[]{};
可以看到這些屬性的效果在效果圖中博主基本都使用出來了
讀取自定義屬性
/**
* 讀取自定義屬性
*
* @param context
* @param attrs
*/
private void readAttr(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.XTabHost);
//獲取自定義屬性
curIndex = (int) a.getInt(R.styleable.XTabHost_default_index, 0);
radius = a.getDimensionPixelSize(R.styleable.XTabHost_radius, dpToPx(radius));
backBg = a.getColor(R.styleable.XTabHost_bg, Color.WHITE);
unSelectTabBg = a.getColor(R.styleable.XTabHost_tab_unselect_color,
Color.parseColor("#51B5EF"));
selectTabBg = a.getColor(R.styleable.XTabHost_tab_select_color, Color.WHITE);
Color.WHITE);
Color.parseColor("#51B5EF"));
textSize = a.getDimensionPixelSize(R.styleable.XTabHost_text_size, 16);
space = a.getDimensionPixelSize(R.styleable.XTabHost_tab_space, 1);
tabWidth = a.getDimensionPixelSize(R.styleable.XTabHost_tab_width,
dpToPx(tabWidth));
tabHeight = a.getDimensionPixelSize(R.styleable.XTabHost_tab_height, -1);
CharSequence[] arr = a.getTextArray(R.styleable.XTabHost_src);
if (arr != null) {
String[] tArr = new String[arr.length];
for (int i = 0; i < arr.length; i++) {
tArr[i] = String.valueOf(arr[i]);
}
textArr = tArr;
}
a.recycle();
}
由於每一個讀取的屬性在上面的定義的時候都有註釋,就不做解釋了
根據支援的屬性生成效果
/**
* 根據所有的引數,弄出效果
*/
private void sove() {
GradientDrawable dd = new GradientDrawable();
//設定圓角
dd.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});
//設定背景顏色
dd.setColor(backBg);
//相容低版本
if (Build.VERSION.SDK_INT >= 16) {
setBackground(dd);
} else {
setBackgroundDrawable(dd);
}
//移除所有的孩子
removeAllViews();
if (curIndex >= textArr.length || curIndex < 0) {
curIndex = 0;
}
for (int i = 0; i < textArr.length; i++) {
//建立一個文字
TextView tv = new TextView(getContext());
//建立文字的佈局物件
LayoutParams params = new LayoutParams(
0, ViewGroup.LayoutParams.MATCH_PARENT
);
if (i > 0) {
params.leftMargin = space;
}
GradientDrawable d = getFitGradientDrawable(i);
//如果選中了設定選中的顏色和背景
if (curIndex == i) {
tv.setTextColor(selectTextColor);
d.setColor(selectTabBg);
} else {
tv.setTextColor(unSelectTextColor);
d.setColor(unSelectTabBg);
}
//設定文字
tv.setText(textArr[i]);
//設定文字顯示在中間
tv.setGravity(Gravity.CENTER);
//設定文字大小
tv.setTextSize(textSize);
//設定文字的背景,相容低版本
if (Build.VERSION.SDK_INT >= 16) {
tv.setBackground(d);
} else {
//noinspection deprecation
tv.setBackgroundDrawable(d);
}
//設定文字(也就是tab)的權重
params.weight = 1;
tv.setLayoutParams(params);
tv.setTag(i);
tv.setOnClickListener(this);
//新增孩子
addView(tv);
}
}
/**
* 獲取每一個tab的背景圖,也就是TextView的背景圖,最左邊是左邊有圓角效果的
* 最右邊是右邊有圓角效果的
* 即是左邊又是右邊的是四個角都有圓角的
*
* @param index tab的下標
* @return
*/
private GradientDrawable getFitGradientDrawable(int index) {
GradientDrawable d = null;
//根據下標決定圓角
if (index == 0 && index == textArr.length - 1) {//如果只有一個的時候
d = new GradientDrawable();
//設定圓角
d.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});
} else if (index == 0) { //如果是最左邊的,那左上角和左下角是圓角
d = new GradientDrawable();
//設定圓角
d.setCornerRadii(new float[]{radius, radius, 0, 0, 0, 0, radius, radius});
} else if (index == textArr.length - 1) {//如果是最右邊的,那右上角和右下角是圓角
d = new GradientDrawable();
//設定圓角
d.setCornerRadii(new float[]{0, 0, radius, radius, radius, radius, 0, 0});
} else { //如果是中間的,那麼沒有圓角
d = new GradientDrawable();
//設定圓角
d.setCornerRadii(new float[]{0, 0, 0, 0, 0, 0, 0, 0});
}
return d;
}
這段程式碼中點說一下
第一段長的方法sove是我們上面分析實現流程中的第三步,根據所有的屬性實現效果
其實很簡單,首先移除所有的孩子,然後根據文字的陣列的個數,新增N個TextView,讓每
一個TextView都是寬度平分父容器,高度填充父容器,這和自己在xml中寫的效果是一樣的
新增的過程中我們需要判斷當前的Tab是否是帶有圓角的,因為我們可以看到效果圖中
左邊的有圓角,右邊的也有,所以方法getFitGradientDrawable(int index);
就是用來獲取指定下標的背景,其實就是獲取每一個TextView應該使用的背景
在for迴圈開始前我們可以看到我們也設定了本身的背景,本身的背景是四個角都有的哦
然後程式碼的最後再新增每一個TextView的點選事件,然後切換下選中的TextView和取消選中
的TextView的效果即可
剩下的程式碼
@Override
public void onClick(View v) {
//拿到下標
int index = (int) v.getTag();
//如果點選的是同一個,不做處理
if (index == curIndex) {
return;
}
//拿到當前的TextView
TextView tv = (TextView) getChildAt(curIndex);
//設定為沒有被選中的文字和沒有被選中的背景
tv.setTextColor(unSelectTextColor);
GradientDrawable d = getFitGradientDrawable(curIndex);
d.setColor(unSelectTabBg);
if (Build.VERSION.SDK_INT >= 16) {
tv.setBackground(d);
} else {
//noinspection deprecation
tv.setBackgroundDrawable(d);
}
//記錄被選中的下標
curIndex = index;
//拿到當前被選中的TextView
tv = (TextView) getChildAt(curIndex);
//設定為被選中的文字和被選中的背景
tv.setTextColor(selectTextColor);
d = getFitGradientDrawable(curIndex);
d.setColor(selectTabBg);
if (Build.VERSION.SDK_INT >= 16) {
tv.setBackground(d);
} else {
//noinspection deprecation
tv.setBackgroundDrawable(d);
}
//如果使用者監聽了就通知一下
if (mOnSelectListener != null) {
mOnSelectListener.onSelect(index, textArr[index]);
}
}
/**
* dp的單位轉換為px的
*
* @param dps
* @return
*/
int dpToPx(int dps) {
return Math.round(getResources().getDisplayMetrics().density * dps);
}
/**
* sp轉px
*
* @param spVal
* @return
*/
int spToPx(float spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
spVal, getResources().getDisplayMetrics());
}
private OnSelectListener mOnSelectListener;
/**
* 設定監聽
*
* @param mOnSelectListener
*/
public void setOnSelectListener(OnSelectListener mOnSelectListener) {
this.mOnSelectListener = mOnSelectListener;
}
/**
* 回撥介面
*/
public interface OnSelectListener {
/**
* 回撥方法
*
* @param index
* @param text
*/
void onSelect(int index, String text);
}
下面貼出所有的程式碼
/**
* Created by cxj on 2017/2/19.
* 模仿qq主介面的選項卡
*/
public class XTabHost extends LinearLayout implements View.OnClickListener {
public XTabHost(Context context) {
this(context, null);
}
public XTabHost(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public XTabHost(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//設定孩子排列的方向是水平的
setOrientation(HORIZONTAL);
//讀取自定義屬性
readAttr(context, attrs);
//顯示效果
sove();
}
/**
* 讀取自定義屬性
*
* @param context
* @param attrs
*/
private void readAttr(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.XTabHost);
//獲取自定義屬性
curIndex = (int) a.getInt(R.styleable.XTabHost_default_index, 0);
radius = a.getDimensionPixelSize(R.styleable.XTabHost_radius, dpToPx(radius));
backBg = a.getColor(R.styleable.XTabHost_bg, Color.WHITE);
unSelectTabBg = a.getColor(R.styleable.XTabHost_tab_unselect_color, Color.parseColor("#51B5EF"));
selectTabBg = a.getColor(R.styleable.XTabHost_tab_select_color, Color.WHITE);
unSelectTextColor = a.getColor(R.styleable.XTabHost_text_unselect_color, Color.WHITE);
selectTextColor = a.getColor(R.styleable.XTabHost_text_select_color, Color.parseColor("#51B5EF"));
textSize = a.getDimensionPixelSize(R.styleable.XTabHost_text_size, 16);
space = a.getDimensionPixelSize(R.styleable.XTabHost_tab_space, 1);
tabWidth = a.getDimensionPixelSize(R.styleable.XTabHost_tab_width, dpToPx(tabWidth));
tabHeight = a.getDimensionPixelSize(R.styleable.XTabHost_tab_height, -1);
CharSequence[] arr = a.getTextArray(R.styleable.XTabHost_src);
if (arr != null) {
String[] tArr = new String[arr.length];
for (int i = 0; i < arr.length; i++) {
tArr[i] = String.valueOf(arr[i]);
}
textArr = tArr;
}
a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//獲取計算模式
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//獲取推薦的寬和高
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
if (modeWidth == MeasureSpec.EXACTLY) { //如果是確定的
} else { //如果是包裹的或者在橫向的列表中
if (tabWidth > -1) {
for (int i = 0; i < getChildCount(); i++) {
TextView view = (TextView) getChildAt(i);
LayoutParams lp = (LayoutParams) view.getLayoutParams();
lp.width = tabWidth;
}
}
}
if (modeHeight == MeasureSpec.EXACTLY) { //如果是確定的
} else { //如果是包裹的或者在縱向的列表中
if (tabHeight > -1) {
for (int i = 0; i < getChildCount(); i++) {
TextView view = (TextView) getChildAt(i);
LayoutParams lp = (LayoutParams) view.getLayoutParams();
lp.height = tabHeight;
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 自身控制元件的背景
*/
private int backBg = Color.WHITE;
/**
* 沒有選中的tab的背景
*/
private int unSelectTabBg = Color.BLUE;
/**
* 選中的tab的背景
*/
private int selectTabBg = Color.WHITE;
/**
* 一個Tab的寬和高,在自身是包裹的時候會被用到
* -1表示不起作用,計算的時候按照包裹孩子處理
* 80是dp的單位
*/
private int tabWidth = 80, tabHeight = -1;
/**
* 未選中的文字的顏色
*/
private int unSelectTextColor = Color.WHITE;
/**
* 選中的文字的顏色
*/
private int selectTextColor = Color.BLUE;
/**
* 預設的字型大小,sp
*/
private int textSize = 16;
/**
* 間距,px
*/
private int space = 1;
/**
* 圓角半徑,dp
*/
private int radius = 0;
/**
* 當前的下標
*/
private int curIndex = 1;
/**
* 所有要顯示的文字
*/
private String[] textArr = new String[]{};
/**
* 根據所有的引數,弄出效果
*/
private void sove() {
GradientDrawable dd = new GradientDrawable();
//設定圓角
dd.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});
//設定背景顏色
dd.setColor(backBg);
//相容低版本
if (Build.VERSION.SDK_INT >= 16) {
setBackground(dd);
} else {
setBackgroundDrawable(dd);
}
//移除所有的孩子
removeAllViews();
if (curIndex >= textArr.length || curIndex < 0) {
curIndex = 0;
}
for (int i = 0; i < textArr.length; i++) {
//建立一個文字
TextView tv = new TextView(getContext());
//建立文字的佈局物件
LayoutParams params = new LayoutParams(
0, ViewGroup.LayoutParams.MATCH_PARENT
);
if (i > 0) {
params.leftMargin = space;
}
GradientDrawable d = getFitGradientDrawable(i);
//如果選中了設定選中的顏色和背景
if (curIndex == i) {
tv.setTextColor(selectTextColor);
d.setColor(selectTabBg);
} else {
tv.setTextColor(unSelectTextColor);
d.setColor(unSelectTabBg);
}
//設定文字
tv.setText(textArr[i]);
//設定文字顯示在中間
tv.setGravity(Gravity.CENTER);
//設定文字大小
tv.setTextSize(textSize);
//設定文字的背景,相容低版本
if (Build.VERSION.SDK_INT >= 16) {
tv.setBackground(d);
} else {
//noinspection deprecation
tv.setBackgroundDrawable(d);
}
//設定文字(也就是tab)的權重
params.weight = 1;
tv.setLayoutParams(params);
tv.setTag(i);
tv.setOnClickListener(this);
//新增孩子
addView(tv);
}
}
/**
* 獲取每一個tab的背景圖,最左邊是左邊有圓角效果的
* 最右邊是右邊有圓角效果的
* 即是左邊又是右邊的是四個角都有圓角的
*
* @param index tab的下標
* @return
*/
private GradientDrawable getFitGradientDrawable(int index) {
GradientDrawable d = null;
//根據下標決定圓角
if (index == 0 && index == textArr.length - 1) {
d = new GradientDrawable();
//設定圓角
d.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});
} else if (index == 0) {
d = new GradientDrawable();
//設定圓角
d.setCornerRadii(new float[]{radius, radius, 0, 0, 0, 0, radius, radius});
} else if (index == textArr.length - 1) {
d = new GradientDrawable();
//設定圓角
d.setCornerRadii(new float[]{0, 0, radius, radius, radius, radius, 0, 0});
} else {
d = new GradientDrawable();
//設定圓角
d.setCornerRadii(new float[]{0, 0, 0, 0, 0, 0, 0, 0});
}
return d;
}
@Override
public void onClick(View v) {
//拿到下標
int index = (int) v.getTag();
//如果點選的是同一個,不做處理
if (index == curIndex) {
return;
}
//拿到當前的TextView
TextView tv = (TextView) getChildAt(curIndex);
//設定為沒有被選中的文字和沒有被選中的背景
tv.setTextColor(unSelectTextColor);
GradientDrawable d = getFitGradientDrawable(curIndex);
d.setColor(unSelectTabBg);
if (Build.VERSION.SDK_INT >= 16) {
tv.setBackground(d);
} else {
//noinspection deprecation
tv.setBackgroundDrawable(d);
}
//記錄被選中的下標
curIndex = index;
//拿到當前被選中的TextView
tv = (TextView) getChildAt(curIndex);
//設定為被選中的文字和被選中的背景
tv.setTextColor(selectTextColor);
d = getFitGradientDrawable(curIndex);
d.setColor(selectTabBg);
if (Build.VERSION.SDK_INT >= 16) {
tv.setBackground(d);
} else {
//noinspection deprecation
tv.setBackgroundDrawable(d);
}
//如果使用者監聽了就通知一下
if (mOnSelectListener != null) {
mOnSelectListener.onSelect(index, textArr[index]);
}
}
/**
* dp的單位轉換為px的
*
* @param dps
* @return
*/
int dpToPx(int dps) {
return Math.round(getResources().getDisplayMetrics().density * dps);
}
/**
* sp轉px
*
* @param spVal
* @return
*/
int spToPx(float spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
spVal, getResources().getDisplayMetrics());
}
private OnSelectListener mOnSelectListener;
/**
* 設定監聽
*
* @param mOnSelectListener
*/
public void setOnSelectListener(OnSelectListener mOnSelectListener) {
this.mOnSelectListener = mOnSelectListener;
}
/**
* 回撥介面
*/
public interface OnSelectListener {
/**
* 回撥方法
*
* @param index
* @param text
*/
void onSelect(int index, String text);
}
}
喜歡博主的朋友可以關注一波哦。。。
有問題及時在評論去留言,博主會第一時間解答的
最近博主也是比較忙,文章中必有不詳細之處,但是最大的好處
就是博主的註釋還是很詳細的,彌補一下吧。。