目的概述
要實現的控件效果如圖(本圖來自小米3)
如果說大家之前實現過的這種控件的,非常希望能和大家交流學習一下。
或者說如果大家知道類似這種效果的開源控件,也非常希望能夠告知一下。謝謝!!
思路演化
- 先講一下我一開始的思路
- 一開始我的想法是定義一個LinearLayout,不斷在裏面添加TextView的控件,在onlayout()方法中進行TextView的佈局,並滑動到最尾端
- 滑動的話,然後通過Scroller實現滾動,當然滾動效果不是很好
- TextView點擊事件,因爲與滑動衝突,通過GestureDetector很好解決了這兩者的關係
- 但是上面這種的效果不是很好,而且還涉及到了佈局邊緣位置的判斷 ,處理得也不是很好。接下來講一下我現在的實現方式,比較簡單,主要採用了HorizontalScrollView佈局實現
- 通過向HorizontalScrollView添加TextView,並滑動到最尾端
- 由於採用HorizontalScrollView,滑動與點擊事件之間的衝突已經被解決,也不用進行邊緣判斷
進一步改造,實現第一個標籤不動,只是其餘標籤滑動的效果。看了一下小米對於這個佈局的實現即可明白,在最外層添加一個RelativeLayout,子佈局爲HorizontalScrollView,並添加第一個佈局標籤固定在左邊遮擋住HorizontalScrollView的第一個標籤即可
最後補充一點關於標籤TextView右邊箭頭的實現,通過自定義一個繼承TextView的控件,並在onDraw()方法中畫出這樣一條路徑
代碼實現
通過上面思路的講解,下面展現實現的代碼及效果圖
- 標籤TabView
public class TabView extends TextView {
/**
* 背景顏色
*/
private int mBgColor;
/**
* 線段顏色
*/
private int mLineColor;
/**
* 畫筆
*/
private Paint mPaint;
/**
* 路徑
*/
private Path mPath;
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
// 清除上次路徑
mPaint.setColor(mBgColor);
canvas.drawPath(mPath, mPaint);
mPaint.setColor(mLineColor);
mPath.reset();
mPath.moveTo(getWidth() - 20 - getHeight() / 2, 0);
mPath.lineTo(getWidth() - 20, getHeight() / 2);
mPath.lineTo(getWidth() - 20 - getHeight() / 2, getHeight());
canvas.drawPath(mPath, mPaint);
}
public void setTab(String tab) {
setText(tab);
}
/**
* 初始化屬性
*/
private void init() {
setGravity(Gravity.CENTER_VERTICAL);
setMaxLines(1);
mBgColor = getContext().getResources().getColor(
R.color.TabBackGroundColor);
mLineColor = getContext().getResources().getColor(R.color.TabLineColor);
setBackgroundColor(mBgColor);
mPath = new Path();
mPaint = new Paint();
mPaint.setStrokeWidth(2);
mPaint.setStyle(Paint.Style.STROKE);
}
public TabView(Context context) {
this(context, null);
}
public TabView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TabView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
}
- 佈局FlowLayout
public class FlowLayout extends RelativeLayout implements View.OnClickListener {
/**
* Tab集合
*/
private List<String> mTabs;
/**
* 存放Tab的橫向HorizontalScrollView
*/
private HorizontalScrollView mHsv;
/**
* 橫向HorizontalScrollView的子佈局
*/
private LinearLayout mHsvInnerLayout;
/**
* 添加一個新的標籤
*
* @param text
*/
public void addTab(String text) {
mTabs.add(text);
if (getHsvInnerLayoutChildCount() == 0) {
// 爲什麼add的TextView的text沒有居中,因爲在addview後,高度爲最高,再後來addview後,高度增加
// 添加了該tv,導致scrolleView最後面的View顯示不全
addView(getTabView(text));
}
mHsvInnerLayout.addView(getTabView(text));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// 滑到尾端
mHsv.smoothScrollTo(mHsvInnerLayout.getWidth(), 0);
}
/**
* 獲取mHsvInnerLayout的子View數量
*
* @return
*/
private int getHsvInnerLayoutChildCount() {
return mHsvInnerLayout.getChildCount();
}
/**
* 根據tab生產View
*
* @param tab
* @return
*/
private View getTabView(String tab) {
View view = LayoutInflater.from(getContext()).inflate(
R.layout.item_file_tag, this, false);
TabView tv = (TabView) view.findViewById(R.id.item_tag);
tv.setTab(tab);
tv.setTag(tab); // 用於點擊事件的判斷
((TextView) tv).setTextColor(getTabColorStateList());
tv.setOnClickListener(this);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
view.setLayoutParams(params);
return view;
}
private void init() {
mTabs = new ArrayList<String>();
// 設置子view垂直方向上居中
setGravity(Gravity.CENTER_HORIZONTAL);
setBackgroundColor(getResources().getColor(R.color.TabBackGroundColor));
initHSV();
}
/**
* 初始化HorizontalScrollView
*/
private void initHSV() {
mHsv = new HorizontalScrollView(getContext());
// 隱藏滾動條
mHsv.setHorizontalScrollBarEnabled(false);
LayoutParams mHsvParams = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
mHsvParams.rightMargin = 50;
mHsvInnerLayout = new LinearLayout(getContext());
LayoutParams mHsvInnerLayoutParams = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
mHsvInnerLayout.setGravity(Gravity.CENTER_VERTICAL);
// 添加子View
mHsv.addView(mHsvInnerLayout, mHsvInnerLayoutParams);
addView(mHsv, mHsvParams);
}
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 獲取ColorStateList對象
*
* @return
*/
private ColorStateList getTabColorStateList() {
ColorStateList csl = new ColorStateList(new int[][] {
{ android.R.attr.state_pressed },
{ -android.R.attr.state_pressed } }, new int[] { Color.BLACK,
Color.GRAY });
return csl;
}
private onTabClickListener mOnTabClickListener;
/**
* tab點擊事件接口
*/
public interface onTabClickListener {
void onTabClick(String tab);
}
public void setOnTabClickListener(onTabClickListener l) {
this.mOnTabClickListener = l;
}
@Override
public void onClick(View v) {
String tab = (String) v.getTag();
if (tab == null)
return;
if (mOnTabClickListener != null) {
mOnTabClickListener.onTabClick(tab);
}
// 移除後面Tab
int index = mTabs.indexOf(tab);
int size = mTabs.size();
// 不作處理
if (index == -1 || index == size - 1) {
return;
}
for (int i = size - 1; i > index; i--) {
mHsvInnerLayout.removeViewAt(i);
mTabs.remove(i);
}
}
}
效果
- Demo下載地址
總結
- 總體來說沒什麼難度,但是還是值得學習記錄一下過程思想
最後
最近可能開始要忙了,不過最近在學習Android開源框架Volley,所以接下來將會對記錄一下我對Volley的理解。對Volley的用法,源碼,設計模式,HTTP,自定義異常等方面做個記錄
最後呢,如果你有其他思路或者開源控件,非常期待你的分享,謝謝。
- 如果本篇文章有哪裏錯誤不足之處,或者哪方面可以做更好的封裝,也希望能夠指出,一起探討學習。