最近想做一個TableView,主要用於展示表格數據,並且要支持滑動和自定義表格內部的元素(子view)樣式
所以先佔個坑,做一些準備工作。
第一步:確定一下需求
左上角的“lock”白色塊,我把它稱爲“lockView”;上方的“abc”列我把它稱爲“horizontalView”;左側的“1234567”列我把它稱爲“verticalView”。中間標爲“GridView”的地方,表示數據域。slide表示滑動方向;lock代表滑動鎖定。例如當該表格發生水平滑動時,左側的“verticalView”是不發生變化的,同理,當發生垂直滑動時,上方的“horizontalView”是不發生變化的。這就是表格的基礎功能。
另外左上角lockView控制了horizontalView的高度和verticalView的寬度,我的設想lockView是一個可自定義的View,用於展示多種不一樣的功能和樣式。
並且horizontalView、verticalView和GridView中每一個子元素(view)都要是可以自定義的。例如在horizontalView中,可以出現好幾個不同的標籤:姓名、年齡、性別、吃飯速度,這時我不一定全部使用文字來描述該標籤,比如性別一欄中我可以使用圖標,再比如吃飯速度一欄在數據域(GridView)中的描述不一定要用枯燥的文字,可以使用顏色標籤,顏色越深,則吃飯速度越快。
第二步:理清楚了以上基本需求,接下來就是研究具體如何實現了。
我設想是自定義一個TableView繼承自FrameLayout,隨後是注意重寫onMeasure方法來設定寬高
public class TableView extends FrameLayout{
private int width,height;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int tempWidth = getMeasuredSize(widthMeasureSpec,true);
int tempHeight = getMeasuredSize(heightMeasureSpec,false);
width = tempWidth;
height = tempHeight;
setMeasuredDimension(tempWidth,tempHeight);
}
/**
* 計算控件的實際大小
* @param length onMeasure方法的參數,widthMeasureSpec或者heightMeasureSpec
* @param isWidth 是寬度還是高度
* @return int 計算後的實際大小
*/
private int getMeasuredSize(int length, boolean isWidth){
// 模式
int specMode = MeasureSpec.getMode(length);
// 尺寸
int specSize = MeasureSpec.getSize(length);
// 計算所得的實際尺寸,要被返回
int retSize = 0;
// 得到兩側的padding(留邊)
int padding = (isWidth? getPaddingLeft()+getPaddingRight():getPaddingTop()+getPaddingBottom());
// 對不同的指定模式進行判斷
if(specMode==MeasureSpec.EXACTLY){ // 顯式指定大小,如40dp或fill_parent
retSize = specSize + padding;
}else{ // 如果使用wrap_content
retSize = (int) (isWidth? DEF_WIDTH + padding : DEF_HEIGHT + padding);
if(specMode==MeasureSpec.AT_MOST){
retSize = Math.min(retSize, specSize);
}
}
return retSize;
}
}
完成確定寬高方法之後,隨後在其中添加控件,horizontalView和verticalView我想通過RecyclerView來實現。也就是在TableView添加兩個RecyclerView。
public class TableView extends FrameLayout {
//本方法在構造方法中調用
private void init(){
horizontal = new RecyclerView(mContext);
vertical = new RecyclerView(mContext);
hAdapter = new Adapter();
vAdapter = new Adapter2();
GridLayoutManager hGrid = new GridLayoutManager(mContext, 1, LinearLayoutManager.HORIZONTAL, false);
GridLayoutManager vGrid = new GridLayoutManager(mContext, 1, LinearLayoutManager.VERTICAL, false);
horizontal.setLayoutManager(hGrid);
horizontal.setAdapter(hAdapter);
vertical.setLayoutManager(vGrid);
vertical.setAdapter(vAdapter);
horizontal.setTag("horizontal");
vertical.setTag("vertical");
addView(horizontal,0);
addView(vertical,0);
}
//添加了View之後要在這裏給每個View配置對應的位置
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
View view = findViewWithTag("horizontal");
view.layout(100,0,right,100);
View view1 = findViewWithTag("vertical");
view1.layout(0,100,100,bottom);
}
}
隨後是按照設想,配置滑動,我可以選擇在TableView中任何一個地方觸發滑動,並且相應的要將horizontalView和verticalView也聯動起來。也就是說需要重寫onTouchEvent來實現聯動
private RecyclerView tmpTouch;
int startX,startY;
@Override
public boolean onTouchEvent(MotionEvent ev) {
//先判斷是垂直滑動還是水平滑動
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
startX = (int) ev.getX();
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int distanceX = (int) (ev.getX() - startX);
int distanceY = (int) (ev.getY() - startY);
if (distanceX < 10 && distanceY < 10) break;
if (Math.abs(distanceX) > Math.abs(distanceY)){
tmpTouch = horizontal;
} else {
tmpTouch = vertical;
}
break;
}
if (tmpTouch != null){
tmpTouch.onTouchEvent(ev);
}
return true;
}
這裏首先實現了滑動衝突的問題解決,其次將滑動事件直接傳遞給對應的RecyclerView。
未完待續
如果大家有興趣可以去GitHub上看看其它大神已經寫好的TableView項目