Android 可横竖滑动的双向表格

前段时间做个项目,要用到一个横向滑动的表格,顺手百度了一下,看到一位哥们的思路挺不错(http://www.javaapk.com/source/4374.html),参考了一下其中的思路,进行了一定的封装和优化。

封装后还没进行过较为详细的测试,如果遇见有bug或者有优化的建议的话,可随时联系我。

其实整个横竖表格的原理实现,竖向的滑动的话,就是listview的滑动,横向的话,则在listview的item里面利用HorizontalScrollView实现横向滑动,然后加以控制,当某一个item里的HorizontalScrollView发生横向滑动的时候,就把所有的item里的HorizontalScrollView也滑动到对应的位置。

我把这个listview和标题行封装到了一个LinearLayout中,封装成了VHTableView。

先看一下怎么用

布局xml如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
   >

<com.demo.zj.vhtableview.VHTableView
    android:id="@+id/vht_table"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

</com.demo.zj.vhtableview.VHTableView>
</RelativeLayout>
布局文件很简单,就这个自定义的view。

接着在activity里的调用,也很简单

<span style="white-space:pre">	</span>VHTableView vht_table=(VHTableView)findViewById(R.id.vht_table);
<span style="white-space:pre">	</span>VHTableAdapter tableAdapter=new VHTableAdapter(this,titleData,contentData);//titleData,contentData这两个数据源自己设置..
        //vht_table.setFirstColumnIsMove(true);//设置第一列是否可移动,默认不可移动
        //vht_table.setShowTitle(false);//设置是否显示标题行,默认显示
        vht_table.setAdapter(tableAdapter);
里面的VHtableAdapter是实现了VHBaseAdapter接口的类,接着来看VHBaseAdapter接口

import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;


public interface VHBaseAdapter {

    //表格内容的行数,不包括标题行
    public int getContentRows() ;

    //列数
    public int getContentColumn();

    //标题的view,这里从0开始,这里要注意,一定要有view返回去,不能为null,每一行
    // 各列的宽度就等于标题行的列的宽度,且边框的话,自己在这里和下文的表格单元格view里面设置
    public View getTitleView(int columnPosition, ViewGroup parent);

    //表格正文的view,行和列都从0开始,宽度的话在载入的时候,默认会是以标题行各列的宽度,高度的话自适应
    public View getTableCellView(int contentRow, int contentColum, View view, ViewGroup parent);


    public Object getItem(int contentRow) ;

    //每一行被点击的时候的回调
    public void OnClickContentRowItem(int row, View convertView);
}
接下来看 VHTableView的具体实现,具体代码如下:




import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import com.demo.zj.R;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


public class VHTableView extends LinearLayout implements HListViewScrollView.ScrollChangedListener{


    private Context context;
    private LayoutInflater inflater;
    //是否显示标题行
    private boolean showTitle;
    //第一列是否可移动
    private boolean firstColumnIsMove;


    //用于显示表格正文内容
    private ListView listView;


    //存放标题行中的每一列的宽度,所有的行里的每一列都是基于标题行的每一列的宽度,都跟标题行的每一列的宽度相等
    private HashMap<String ,Integer> widthMap=new HashMap<>();

    //存放所有的HScrollView
    protected List<HListViewScrollView> mHScrollViews =new ArrayList<HListViewScrollView>();



    public VHTableView(Context context) {
        this(context,null);
    }

    public VHTableView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    private void init() {
        this.inflater = LayoutInflater.from(context);
        //默认显示标题行
        showTitle=true;
        //默认第一列不可滑动
        firstColumnIsMove=false;
    }

    //设置是否显示标题
    public void setShowTitle(boolean showTitle) {
        this.showTitle = showTitle;
    }

    //设置第一列是否可以滑动
    public void setFirstColumnIsMove(boolean firstColumnIsMove) {
        this.firstColumnIsMove = firstColumnIsMove;
    }



    //设置adapter
    public void setAdapter(final VHBaseAdapter conentAdapter) {
        //清除各原有数据
        cleanup();
        //载入标题行
        initTitles(conentAdapter);
        //载入表格正文
        initContentList(conentAdapter);
        if(!showTitle){
            //假如设置了不显示标题行,在这里隐藏掉
          getChildAt(0).setVisibility(View.GONE);
        }

    }
    public void cleanup(){
        removeAllViews();
        widthMap.clear();
        mHScrollViews.clear();
    }

    private void initTitles(VHBaseAdapter conentAdapter) {
        View titleView=inflater.inflate(R.layout.vhtable_item_listview,this,false);
        LinearLayout  ll_firstcolumn=(LinearLayout)titleView.findViewById(R.id.ll_firstcolumn);
        int i=0;
        if(firstColumnIsMove){
            //假如设置是可移动的,则把不可移动的部分ll_firstcolumn隐藏掉,把所有数据都加在HListViewScrollView中
            ll_firstcolumn.setVisibility(View.GONE);
        }else {
            //假如是设置了第一列不可移动的,则把第一列的数据加到ll_firstcolumn中,其余的都加到HListViewScrollView中
            ll_firstcolumn.removeAllViews();
            View view =conentAdapter.getTitleView(0,ll_firstcolumn);
            //测量view的高度,都采用自适应的模式测量
            view.measure(0,0);
            ll_firstcolumn.addView(view, LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
            //存起来以便设置表格正文的时候进行宽度设置
            widthMap.put("0",view.getMeasuredWidth());
            //之后的titleview就都放在CHListViewScrollView中,因为0 title已经设置了,所以从1开始
             i=1;
        }


            HListViewScrollView chs_datagroup=(HListViewScrollView)titleView.findViewById(R.id.chs_datagroup);
            //把CHListViewScrollView加入管理
            addHViews(chs_datagroup);

            LinearLayout ll_datagroup=(LinearLayout)titleView.findViewById(R.id.ll_datagroup);

            ll_datagroup.removeAllViews();
            for(;i<conentAdapter.getContentColumn();i++){
                View view=conentAdapter.getTitleView(i,ll_datagroup);
                view.measure(0,0);
                ll_datagroup.addView(view,LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
                widthMap.put(i+"", view.getMeasuredWidth());
            }
            addView(titleView);

    }




    private void initContentList(VHBaseAdapter conentAdapter) {
        listView =new ListView(context);
        //设置间隔线为0,以便在view中自己定义间隔线
        listView.setDividerHeight(0);
        ContentAdapter adapter=new ContentAdapter(conentAdapter);
        listView.setAdapter(adapter);
        addView(listView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }


    public void addHViews(final HListViewScrollView hScrollView) {
        if(!mHScrollViews.isEmpty()) {
            int size = mHScrollViews.size();
            HListViewScrollView scrollView = mHScrollViews.get(size - 1);
            final int scrollX = scrollView.getScrollX();
            //这是给第一次满屏,或者快速下滑等情况时,新创建的会再创建一个convertView的时候,把这个新进入的convertView里的HListViewScrollView移到对应的位置
            if(scrollX != 0) {
                listView.post(new Runnable() {
                    @Override
                    public void run() {
                        //在主线程中去移动到对应的位置
                        hScrollView.scrollTo(scrollX, 0);
                    }
                });
            }
        }
        hScrollView.setScrollChangedListener(this);
        mHScrollViews.add(hScrollView);
    }


    private HListViewScrollView currentTouchView;
    @Override
    public void setCurrentTouchView(HListViewScrollView currentTouchView) {
        this.currentTouchView=currentTouchView;
    }

    @Override
    public HListViewScrollView getCurrentTouchView() {
        return currentTouchView;
    }

    @Override
    public void onUIScrollChanged(int l, int t, int oldl, int oldt) {
        for(HListViewScrollView scrollView : mHScrollViews) {
            //防止重复滑动
            if(currentTouchView != scrollView)
                scrollView.smoothScrollTo(l, t);
        }
    }

    public class  ContentAdapter extends BaseAdapter {
        private VHBaseAdapter conentAdapter;

        public ContentAdapter(VHBaseAdapter conentAdapter) {
            this.conentAdapter=conentAdapter;
        }

        @Override
        public int getCount() {
            return conentAdapter.getContentRows();
        }

        @Override
        public Object getItem(int position) {
            return conentAdapter.getItem(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(final int position, View convertView, final ViewGroup parent) {
            ViewHolder viewHolder;
            int maxHeight=0;
            if(null==convertView){
                convertView=inflater.inflate(R.layout.vhtable_item_listview,parent,false);

                HListViewScrollView chs_datagroup=(HListViewScrollView)convertView.findViewById(R.id.chs_datagroup);
                //把CHListViewScrollView加入管理
                addHViews(chs_datagroup);
                viewHolder=new ViewHolder();
                viewHolder.views = new View[conentAdapter.getContentColumn()];
                viewHolder.ll_firstcolumn=(LinearLayout)convertView.findViewById(R.id.ll_firstcolumn);
                viewHolder.ll_datagroup=(LinearLayout)convertView.findViewById(R.id.ll_datagroup);
                viewHolder.rowClickListener=new RowClickListener();
                convertView.setTag(viewHolder);
            }else {
                viewHolder= (ViewHolder) convertView.getTag();
            }
            //更新每行的views的数据
            updateViews(conentAdapter,viewHolder,viewHolder.ll_firstcolumn,viewHolder.ll_datagroup,position);
            //更熟的views数据后重新测量高度,取那一行的最大高度作为整行的高度
            maxHeight=getMaxHeight(conentAdapter,viewHolder.views);
            //重新更新view到表格上
            updateUI(conentAdapter,viewHolder.ll_firstcolumn,viewHolder.ll_datagroup,viewHolder.views,maxHeight);

            //为了尽可能少的影响ScrollView的触摸事件,所以点击事件这里取个巧,直接设置在这两个Linearlayout上
            viewHolder.rowClickListener.setData(conentAdapter,position,convertView);
            viewHolder.ll_firstcolumn.setOnClickListener(viewHolder.rowClickListener);
            viewHolder.ll_datagroup.setOnClickListener(viewHolder.rowClickListener);
            return convertView;
        }


    }


    private ViewHolder updateViews(VHBaseAdapter conentAdapter, ViewHolder viewHolder,LinearLayout ll_firstcolumn, LinearLayout ll_datagroup,int row) {
        for(int i=0;i<conentAdapter.getContentColumn();i++) {

            if(!firstColumnIsMove&&i==0){
                View view= conentAdapter.getTableCellView(row, 0, viewHolder.views[0], ll_firstcolumn);
                viewHolder.views[0]=view;
            }else {
                View view=conentAdapter.getTableCellView(row, i, viewHolder.views[i], ll_datagroup);
                viewHolder.views[i]=view;
            }
        }
        return viewHolder;
    }

    private int getMaxHeight(VHBaseAdapter conentAdapter,View[] views) {
        int maxHeight=0;
        for(int i=0;i<conentAdapter.getContentColumn();i++) {
                //测量模式:宽度以标题行各列的宽度为准,高度为自适应
                int w = View.MeasureSpec.makeMeasureSpec(widthMap.get(""+i), MeasureSpec.EXACTLY);
                int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
                views[i].measure(w,h);
                maxHeight = Math.max(maxHeight, views[i].getMeasuredHeight());
        }
        return maxHeight;
    }

    private void updateUI(VHBaseAdapter conentAdapter,  LinearLayout ll_firstcolumn, LinearLayout ll_datagroup,View[] views,int maxHeight) {
        //其实这里可以优化一下,不用remove掉全部又加一次,以后再优化一下。。。。
        ll_firstcolumn.removeAllViews();
        ll_datagroup.removeAllViews();
        for(int i=0;i<conentAdapter.getContentColumn();i++) {

            if(!firstColumnIsMove&&i==0){
                ll_firstcolumn.addView(views[0],widthMap.get("0"),maxHeight);
            }else {
                ll_datagroup.addView(views[i],widthMap.get(""+i),maxHeight);
            }
        }

    }

    public class RowClickListener implements OnClickListener {

        private VHBaseAdapter conentAdapter;
        private int row;
        private View convertView;
        public void setData(VHBaseAdapter conentAdapter, int row, View convertView){
            this.conentAdapter=conentAdapter;
            this.row=row;
            this.convertView=convertView;
        }
        @Override
        public void onClick(View v) {
            if(null!=conentAdapter&&null!=convertView){
                conentAdapter.OnClickContentRowItem(row,convertView);
            }
        }
    }

public class ViewHolder {
    LinearLayout  ll_firstcolumn;
    LinearLayout ll_datagroup;
    View[] views;
    RowClickListener rowClickListener;
}



}
这里用到的布局文件vhtable_item_listview如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/row_hlistview"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <LinearLayout
        android:id="@+id/ll_firstcolumn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal"
        >

    </LinearLayout>
    <com.demo.zj.vhtableview.HListViewScrollView
        android:id="@+id/chs_datagroup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:clickable="false"
        android:scrollbars="none"
       >

        <LinearLayout
            android:id="@+id/ll_datagroup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="horizontal">
        </LinearLayout>
    </com.demo.zj.vhtableview.HListViewScrollView>

</LinearLayout>
稍作修改的 HorizontalScrollView如下



import android.content.Context;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.HorizontalScrollView;

public class HListViewScrollView extends HorizontalScrollView {


	public HListViewScrollView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	
	public HListViewScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public HListViewScrollView(Context context) {
		super(context);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		//记录当前触摸的HListViewScrollView
		if(null!=this.listener){
			listener.setCurrentTouchView(this);
		}
		return super.onTouchEvent(ev);
	}
	
	@Override
	protected void onScrollChanged(int l, int t, int oldl, int oldt) {
		//当当前的HListViewScrollView被触摸时,滑动其它
		if(null!=this.listener&&null!=listener.getCurrentTouchView()&&listener.getCurrentTouchView() == this) {
			listener.onUIScrollChanged(l, t, oldl, oldt);
		}else{
			super.onScrollChanged(l, t, oldl, oldt);
		}
	}

	private ScrollChangedListener listener;
	public void setScrollChangedListener(ScrollChangedListener listener){
		this.listener=listener;
	}

	public interface ScrollChangedListener{

		public void setCurrentTouchView(HListViewScrollView currentTouchView);

		public HListViewScrollView getCurrentTouchView();
		public void onUIScrollChanged(int l, int t, int oldl, int oldt);
	}
}

里面的注释其实说的挺清楚了,这里说一下大概的思路,说白了就是一个 LinearLayout,里面添加了两个子view,一个标题行,一个listview,标题行和listview的item用的都是同一个布局,这个布局里面也是一个 LinearLayout横向包含两个子view,一个普通的 LinearLayout和一个 HListViewScrollView,当设置第一列不可移动的时候,就把第一列放到那个普通的 LinearLayout里,不然就全扔到HListViewScrollView里。

然后就把标题行里跟listview里面所有创建了的HListViewScrollView都放一个List里面,然后监控各个HListViewScrollView的横向滑动事件,某一个滑动的时候,其他的也跟着滑动。

在加入标题行的view的时候,就进行控件的测量,并把宽度记下来,然后下面listview里面的每一列就都跟标题行一致,高度则全都是自适应。

其实东西不多,就这些东西,稍微封装优化了一下。。。。。


源码Demo:

https://git.oschina.net/jiancode/VHTable.git

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值