diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..609aa00 --- /dev/null +++ b/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2bdab58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +gen +bin/* + diff --git a/.project b/.project new file mode 100644 index 0000000..1f3d24e --- /dev/null +++ b/.project @@ -0,0 +1,33 @@ + + + ZPagedHScrollTable + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..11c5b79 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.markdown b/README.markdown index 0314a3b..ba2ee2a 100644 --- a/README.markdown +++ b/README.markdown @@ -2,9 +2,17 @@ ZPagedHScrollTable 是一个 Android 表格控件, 具备手势翻页功能, 并能支持对总宽度大于展现区域的数据列进行横向滚动, 实现移动设备上对大数据集的直观展示. +# 使用方法 + +1. 在界面 Layout XML 中添加 Custom & Library Views 组中的 ZPagedHScrollTable 控件 +1. 实现 ZPagedHScrollTableDataAdapter 数据适配器接口,完成其中各个数据获取回调方法 +1. 设置 ZPagedHScrollTable 控件的每页显示记录数和数据适配器对象,并调用其 refreshTable 方法完成内容显示. 后续翻页时, 控件会自行调用数据适配器完成数据加载, 不需要主控程序干预. + +demo/ 目录下为一个实际的例子, 可供参考. + # 授权信息 - 本项目为 BSD 授权. 以下为正式的授权说明文本: +本项目为 BSD 授权. 以下为正式的授权说明文本: Copyright (C) 2011 HandStar Co., Ltd. ( http://zhangxing.info ). All rights reserved. diff --git a/default.properties b/default.properties new file mode 100644 index 0000000..e2e8061 --- /dev/null +++ b/default.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-8 diff --git a/proguard.cfg b/proguard.cfg new file mode 100644 index 0000000..12dd039 --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,36 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..8074c4c Binary files /dev/null and b/res/drawable-hdpi/icon.png differ diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png new file mode 100644 index 0000000..1095584 Binary files /dev/null and b/res/drawable-ldpi/icon.png differ diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..a07c69f Binary files /dev/null and b/res/drawable-mdpi/icon.png differ diff --git a/res/drawable/zpaged_hscroll_table_down_arrow.png b/res/drawable/zpaged_hscroll_table_down_arrow.png new file mode 100644 index 0000000..0545021 Binary files /dev/null and b/res/drawable/zpaged_hscroll_table_down_arrow.png differ diff --git a/res/drawable/zpaged_hscroll_table_left_arrow.png b/res/drawable/zpaged_hscroll_table_left_arrow.png new file mode 100644 index 0000000..c3e1f2f Binary files /dev/null and b/res/drawable/zpaged_hscroll_table_left_arrow.png differ diff --git a/res/drawable/zpaged_hscroll_table_right_arrow.png b/res/drawable/zpaged_hscroll_table_right_arrow.png new file mode 100644 index 0000000..4369548 Binary files /dev/null and b/res/drawable/zpaged_hscroll_table_right_arrow.png differ diff --git a/res/drawable/zpaged_hscroll_table_up_arrow.png b/res/drawable/zpaged_hscroll_table_up_arrow.png new file mode 100644 index 0000000..f124616 Binary files /dev/null and b/res/drawable/zpaged_hscroll_table_up_arrow.png differ diff --git a/res/layout/demo.xml b/res/layout/demo.xml new file mode 100644 index 0000000..8a8c89b --- /dev/null +++ b/res/layout/demo.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/res/layout/zpaged_hscroll_table.xml b/res/layout/zpaged_hscroll_table.xml new file mode 100644 index 0000000..d87a41b --- /dev/null +++ b/res/layout/zpaged_hscroll_table.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/zpaged_hscroll_table_col_view_a.xml b/res/layout/zpaged_hscroll_table_col_view_a.xml new file mode 100644 index 0000000..c91fe46 --- /dev/null +++ b/res/layout/zpaged_hscroll_table_col_view_a.xml @@ -0,0 +1,6 @@ + + + + diff --git a/res/layout/zpaged_hscroll_table_col_view_b.xml b/res/layout/zpaged_hscroll_table_col_view_b.xml new file mode 100644 index 0000000..ad8253d --- /dev/null +++ b/res/layout/zpaged_hscroll_table_col_view_b.xml @@ -0,0 +1,6 @@ + + + + diff --git a/res/layout/zpaged_hscroll_table_fixed_row.xml b/res/layout/zpaged_hscroll_table_fixed_row.xml new file mode 100644 index 0000000..206e804 --- /dev/null +++ b/res/layout/zpaged_hscroll_table_fixed_row.xml @@ -0,0 +1,7 @@ + + + + diff --git a/res/layout/zpaged_hscroll_table_head_view.xml b/res/layout/zpaged_hscroll_table_head_view.xml new file mode 100644 index 0000000..7ca2494 --- /dev/null +++ b/res/layout/zpaged_hscroll_table_head_view.xml @@ -0,0 +1,6 @@ + + + + diff --git a/res/layout/zpaged_hscroll_table_scrollable_row.xml b/res/layout/zpaged_hscroll_table_scrollable_row.xml new file mode 100644 index 0000000..8f25406 --- /dev/null +++ b/res/layout/zpaged_hscroll_table_scrollable_row.xml @@ -0,0 +1,7 @@ + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..91bd4cb --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Hello World, ZPagedHScrollTableActivity! + ZPagedHScrollTable + diff --git a/res/values/zpaged_hscroll_table_colors.xml b/res/values/zpaged_hscroll_table_colors.xml new file mode 100644 index 0000000..f606df7 --- /dev/null +++ b/res/values/zpaged_hscroll_table_colors.xml @@ -0,0 +1,4 @@ + + + #ffcfe1f5 + diff --git a/res/values/zpaged_hscroll_table_styles.xml b/res/values/zpaged_hscroll_table_styles.xml new file mode 100644 index 0000000..b70bd82 --- /dev/null +++ b/res/values/zpaged_hscroll_table_styles.xml @@ -0,0 +1,44 @@ + + + + + + diff --git a/src/demo/DemoActivity.java b/src/demo/DemoActivity.java new file mode 100644 index 0000000..4a07a29 --- /dev/null +++ b/src/demo/DemoActivity.java @@ -0,0 +1,99 @@ +package demo; + +import info.zhangxing.R; +import info.zhangxing.ZPagedHScrollTable; +import info.zhangxing.ZPagedHScrollTableDataAdapter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Toast; + +public class DemoActivity extends Activity { + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.demo); + + ZPagedHScrollTable pht = (ZPagedHScrollTable) findViewById(R.id.pagedHScrollTable1); + pht.setPageSize(10); // 设置每页展现记录数 + pht.setAdapter(new DataAdaptor()); + + // 刷新表格,展现数据 + pht.refreshTable(); + } + + class DataAdaptor implements ZPagedHScrollTableDataAdapter { + + Map colHeaders; + + { + colHeaders = new HashMap(); + colHeaders.put(ZPagedHScrollTableDataAdapter.FIXED_COLUMN, + new String[] { "固定列A", "固定列B" }); + colHeaders.put(ZPagedHScrollTableDataAdapter.SCROLLABLE_COLUMN, + new String[] { "滑动列A1", "滑动列B1", "滑动列C1", "滑动列A2", "滑动列B2", + "滑动列C2", "滑动列A3", "滑动列B3", "滑动列C3" }); + } + + @Override + public Map getColHeaders() { + return colHeaders; + } + + @Override + public int getTotalRows() { + // 整个数据集共30行 + return 30; + } + + @Override + public List>> getRows(int startRow, int endRow) { + List>> rows = new ArrayList>>(); + for (int i = startRow; i <= endRow; ++i) { + Map> row = new HashMap>(); + + List fCols = new ArrayList(); + fCols.add("固定列A数据" + i); + fCols.add("固定列B数据" + i); + row.put(ZPagedHScrollTableDataAdapter.FIXED_COLUMN, fCols); + + List sCols = new ArrayList(); + for (int j = 0; j < 9; ++j) { + sCols.add("滑动列" + j + "数据" + i); + } + row.put(ZPagedHScrollTableDataAdapter.SCROLLABLE_COLUMN, sCols); + + rows.add(row); + } + + // 模拟数据获取延迟 + try { + Thread.sleep(200); + } catch (Exception e) { + } + + return rows; + } + + @Override + public OnClickListener getOnClickListener(final int row) { + return new OnClickListener() { + @Override + public void onClick(View v) { + Toast.makeText(DemoActivity.this, "你点击了第" + row + "行", + Toast.LENGTH_SHORT).show(); + } + }; + } + + } +} diff --git a/src/info/zhangxing/ZPagedHScrollTable.java b/src/info/zhangxing/ZPagedHScrollTable.java new file mode 100644 index 0000000..0cb9206 --- /dev/null +++ b/src/info/zhangxing/ZPagedHScrollTable.java @@ -0,0 +1,478 @@ +package info.zhangxing; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.app.ProgressDialog; +import android.content.Context; +import android.graphics.Color; +import android.os.AsyncTask; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TableLayout; +import android.widget.TableRow; +import android.widget.TextView; +import android.widget.Toast; + +/** + * 带分页功能的可滚动列数据表格 + * + * FIXME: ProgressDialog 正在显示时若改变设备方向,会导致出错退出...目前只能在 AndroidManifest.xml 中设置 + * screen 保持特定方向(portrait/landscape) + * + * @author open@zhangxing.info + * + */ +public class ZPagedHScrollTable extends FrameLayout { + private static final float OFFSET_THRESHOLD_DP = 30.0f; // 判断上下翻页的位移阈值(单位dp) + + private float pixelPerDp; + private int pageSize = 10; // 表格每页显示记录条数,默认为 10 + private int totalNum = 0; // 待显示总记录条数 + private int startRow = 0; // 起始行号 + private int endRow = 0; // 结束行号 + private ZPagedHScrollTableDataAdapter adapter = null; // 使用方传入的数据适配器对象 + + private LayoutInflater layoutInflater; + private ImageView upIndicator; + private ImageView downIndicator; + private ImageView leftIndicator; + private ImageView rightIndicator; + + private float startX = 0.0f; + private float startY = 0.0f; + private float endX = 0.0f; + private float endY = 0.0f; + + /** + * 获取每页展现记录数 + * + * @return 当前设置的每页显示记录条数 + */ + public int getPageSize() { + return pageSize; + } + + /** + * 设置每页展现记录数 + * + * @param pageSize + * 每页展现记录数 + */ + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } + + /** + * 获取当前所用的表格数据适配器对象 + * + * @return 表格数据适配器对象 + */ + public ZPagedHScrollTableDataAdapter getAdapter() { + return adapter; + } + + /** + * 设置表格数据适配器对象 + * + * @param adapter + * 待使用的表格数据适配器对象 + */ + public void setAdapter(ZPagedHScrollTableDataAdapter adapter) { + this.adapter = adapter; + } + + /** + * 强制重置并刷新表格 + */ + public void refreshTable() { + new InitTableStruct().execute((Void[]) null); + } + + public ZPagedHScrollTable(Context context, AttributeSet attrs) { + super(context, attrs); + + // 当前设备屏幕 1 dp 对应的像素数 + pixelPerDp = getContext().getResources().getDisplayMetrics().density; + + init(context); + } + + private void init(Context context) { + // 从 Layout XML 文件中加载控件 + layoutInflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + layoutInflater.inflate(R.layout.zpaged_hscroll_table, this); + + upIndicator = (ImageView) findViewById(R.id.upIndicator); + downIndicator = (ImageView) findViewById(R.id.downIndicator); + leftIndicator = (ImageView) findViewById(R.id.leftIndicator); + rightIndicator = (ImageView) findViewById(R.id.rightIndicator); + + // 创建时表格总是位于首页,可滚动区域位于最左边,控件刚创建时没有内容,故隐藏对应方向的指示符 + upIndicator.setVisibility(INVISIBLE); + downIndicator.setVisibility(INVISIBLE); + + ZPagedHScrollTableHSV scrollableArea = (ZPagedHScrollTableHSV) findViewById(R.id.scrollableArea); + scrollableArea.setLeftIndicator(leftIndicator); + scrollableArea.setRightIndicator(rightIndicator); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + handleTouchEvent(ev); + return false; + } + + private class InitTableStruct extends AsyncTask { + ProgressDialog waitDiag; + + @Override + protected void onPreExecute() { + waitDiag = ProgressDialog.show(getContext(), "请稍等...", + "获取数据中,请稍等..."); + } + + @Override + protected PageData doInBackground(Void... params) { + totalNum = adapter.getTotalRows(); + + startRow = 0; + endRow = (totalNum < pageSize ? totalNum : pageSize) - 1; + + PageData pageData = new PageData(); + pageData.headData = adapter.getColHeaders(); + pageData.rowData = adapter.getRows(startRow, endRow); + pageData.rowOnClick = new ArrayList(); + + int dataSize = pageData.rowData.size(); + for (int i = 0; i < dataSize; ++i) { + pageData.rowOnClick.add(adapter + .getOnClickListener(startRow + i)); + } + endRow = startRow + dataSize - 1; + + return pageData; + } + + @Override + protected void onPostExecute(PageData pageData) { + // FIXME: 若不将表格背景色设为透明,在清空表格后会出现两条竖线,需要考虑有没有在 XML layout + // 文件中直接更改的方法... + TableLayout fixedTable = (TableLayout) findViewById(R.id.fixedTable); + TableLayout scrollableTable = (TableLayout) findViewById(R.id.scrollableTable); + fixedTable.setBackgroundColor(Color.argb(0, 0, 0, 0)); + scrollableTable.setBackgroundColor(Color.argb(0, 0, 0, 0)); + + setTableHead(pageData); + setTableData(pageData); + + fixedTable + .setBackgroundResource(R.color.zPagedHScrollTableBorderColor); + scrollableTable + .setBackgroundResource(R.color.zPagedHScrollTableBorderColor); + + waitDiag.dismiss(); + } + + } + + private class ShowPage extends AsyncTask { + ProgressDialog waitDiag; + + @Override + protected void onPreExecute() { + waitDiag = ProgressDialog.show(getContext(), "请稍候...", "获取数据中..."); + } + + @Override + protected PageData doInBackground(Void... params) { + PageData pageData = new PageData(); + pageData.headData = adapter.getColHeaders(); + pageData.rowData = adapter.getRows(startRow, endRow); + pageData.rowOnClick = new ArrayList(); + + int dataSize = pageData.rowData.size(); + for (int i = 0; i < dataSize; ++i) { + pageData.rowOnClick.add(adapter + .getOnClickListener(startRow + i)); + } + endRow = startRow + dataSize - 1; + + return pageData; + } + + @Override + protected void onPostExecute(PageData result) { + setTableData(result); + waitDiag.dismiss(); + } + + } + + private void clearHeadRow() { + int[] tables = { R.id.fixedTable, R.id.scrollableTable }; + for (int id : tables) { + TableLayout table = (TableLayout) findViewById(id); + // 清空表头 + TableRow headRow = (TableRow) table.getChildAt(0); + headRow.removeAllViews(); + } + } + + private void clearDataRow() { + int[] tables = { R.id.fixedTable, R.id.scrollableTable }; + for (int id : tables) { + TableLayout table = (TableLayout) findViewById(id); + // 删除除表头以外的所有数据行 + ArrayList rows = new ArrayList(); + for (int i = 1; i < table.getChildCount(); ++i) { + rows.add(table.getChildAt(i)); + } + for (View row : rows) { + table.removeView(row); + } + } + } + + private void setTableHead(PageData pageData) { + clearHeadRow(); + + Map colHeaders = pageData.headData; + TableRow fixedColHeadRow = (TableRow) findViewById(R.id.fixedColHeadRow); + boolean firstCol = true; + for (String colHead : colHeaders + .get(ZPagedHScrollTableDataAdapter.FIXED_COLUMN)) { + // 填充固定列表头 + TextView head = (TextView) layoutInflater.inflate( + R.layout.zpaged_hscroll_table_head_view, null); + head.setText(colHead); + + TableRow.LayoutParams layoutParams = new TableRow.LayoutParams( + TableRow.LayoutParams.FILL_PARENT, + TableRow.LayoutParams.FILL_PARENT, 1.0f); + + if (firstCol) { + firstCol = false; + layoutParams.setMargins(0, 0, 0, 0); + } else { + layoutParams.setMargins(d2p(1), 0, 0, 0); + } + head.setLayoutParams(layoutParams); + + fixedColHeadRow.addView(head); + } + + TableRow scrollableColHeadRow = (TableRow) findViewById(R.id.scrollableColHeadRow); + firstCol = true; + for (String colHead : colHeaders + .get(ZPagedHScrollTableDataAdapter.SCROLLABLE_COLUMN)) { + TextView head = (TextView) layoutInflater.inflate( + R.layout.zpaged_hscroll_table_head_view, null); + head.setText(colHead); + + TableRow.LayoutParams layoutParams = new TableRow.LayoutParams( + TableRow.LayoutParams.FILL_PARENT, + TableRow.LayoutParams.FILL_PARENT, 1.0f); + if (firstCol) { + firstCol = false; + layoutParams.setMargins(0, 0, 0, 0); + } else { + layoutParams.setMargins(d2p(1), 0, 0, 0); + } + head.setLayoutParams(layoutParams); + + scrollableColHeadRow.addView(head); + } + } + + private void setTableData(PageData pageData) { + clearDataRow(); + if (startRow > 0) { + upIndicator.setVisibility(VISIBLE); + } else { + upIndicator.setVisibility(INVISIBLE); + } + if (endRow < totalNum - 1) { + downIndicator.setVisibility(VISIBLE); + } else { + downIndicator.setVisibility(INVISIBLE); + } + + int[] layoutIds = { R.layout.zpaged_hscroll_table_fixed_row, + R.layout.zpaged_hscroll_table_scrollable_row }; + Integer[] colTypes = { ZPagedHScrollTableDataAdapter.FIXED_COLUMN, + ZPagedHScrollTableDataAdapter.SCROLLABLE_COLUMN }; + TableLayout[] tables = { (TableLayout) findViewById(R.id.fixedTable), + (TableLayout) findViewById(R.id.scrollableTable) }; + + // 准备表格填充空行数据 + Map> dummyRow = new HashMap>(); + String[] fHead = pageData.headData + .get(ZPagedHScrollTableDataAdapter.FIXED_COLUMN); + String[] sHead = pageData.headData + .get(ZPagedHScrollTableDataAdapter.SCROLLABLE_COLUMN); + if (fHead != null) { + dummyRow.put(ZPagedHScrollTableDataAdapter.FIXED_COLUMN, + Arrays.asList(fHead)); + } else { + dummyRow.put(ZPagedHScrollTableDataAdapter.FIXED_COLUMN, null); + } + if (sHead != null) { + dummyRow.put(ZPagedHScrollTableDataAdapter.SCROLLABLE_COLUMN, + Arrays.asList(sHead)); + } else { + dummyRow.put(ZPagedHScrollTableDataAdapter.SCROLLABLE_COLUMN, null); + } + + int actualDataSize = pageData.rowData.size(); + boolean isRowA = true; + for (int i = 0; i < pageSize; ++i) { + Map> rowData = null; + if (i < actualDataSize) { + rowData = pageData.rowData.get(i); + } else { + rowData = dummyRow; + } + + // 顺序填充固定列和滑动列数据 + for (int areaId = 0; areaId < layoutIds.length; ++areaId) { + List cols = rowData.get(colTypes[areaId]); + + TableRow rowLayout = (TableRow) layoutInflater.inflate( + layoutIds[areaId], null); + TableLayout.LayoutParams rowLayoutParams = new TableLayout.LayoutParams( + TableLayout.LayoutParams.WRAP_CONTENT, + TableLayout.LayoutParams.FILL_PARENT, 1.0f); + if (colTypes[areaId] == ZPagedHScrollTableDataAdapter.FIXED_COLUMN) { + // 固定列数据行布局属性 + rowLayoutParams.setMargins(d2p(1), 0, d2p(1), d2p(1)); + } else { + // 滑动列数据行布局属性 + rowLayoutParams.setMargins(0, 0, d2p(1), d2p(1)); + + } + rowLayout.setLayoutParams(rowLayoutParams); + + boolean firstCol = true; + for (String col : cols) { + TextView cell = null; + + // 以不同的配色分隔相邻行 + if (isRowA) { + cell = (TextView) layoutInflater.inflate( + R.layout.zpaged_hscroll_table_col_view_a, null); + } else { + cell = (TextView) layoutInflater.inflate( + R.layout.zpaged_hscroll_table_col_view_b, null); + } + cell.setText(col); + // XXX: 让超出列宽的文本开始走马灯动画展示 + cell.setSelected(true); + // 隐藏超过有效数据行范围的填充文本 + if (i >= actualDataSize) { + cell.setTextColor(Color.argb(0, 0, 0, 0)); + } + + TableRow.LayoutParams layoutParams = new TableRow.LayoutParams( + TableRow.LayoutParams.WRAP_CONTENT, + TableRow.LayoutParams.FILL_PARENT, 1.0f); + if (firstCol) { + firstCol = false; + layoutParams.setMargins(0, 0, 0, 0); + } else { + layoutParams.setMargins(d2p(1), 0, 0, 0); + } + cell.setLayoutParams(layoutParams); + + rowLayout.addView(cell); + } + + // 为有效数据行范围内的行添加点击处理器 + if (i < actualDataSize) { + rowLayout.setOnClickListener(pageData.rowOnClick.get(i)); + } else { + // XXX: 空白区域的 TableRow 也必须有 OnClickListener,才能正常识别固定列向上翻页动作 + rowLayout.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + } + }); + } + + tables[areaId].addView(rowLayout); + } + + // 切换行背景配色 + isRowA = !isRowA; + } + } + + private void handleTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + startX = endX = event.getX(); + startY = endY = event.getY(); + break; + case MotionEvent.ACTION_UP: + // Y 轴滑动速度高于阈值时触发上下翻页操作 + endX = event.getX(); + endY = event.getY(); + if (Math.abs(startX - endX) < Math.abs(startY - endY)) { + // X 轴位移没有 Y 轴位移大,若 Y 轴位移达到阈值则触发上下翻页操作 + float offY = endY - startY; + if (offY > d2p(OFFSET_THRESHOLD_DP)) { + doPageUp(); + } else if (offY < -d2p(OFFSET_THRESHOLD_DP)) { + doPageDown(); + } + } + break; + case MotionEvent.ACTION_CANCEL: + break; + } + } + + private void doPageDown() { + if (endRow >= 0 && endRow < totalNum - 1 && pageSize > 0) { + startRow = endRow + 1; + endRow += pageSize; + if (endRow > totalNum - 1) { + endRow = totalNum - 1; + } + new ShowPage().execute((Void[]) null); + } else { + String msg = "已经是最后一页"; + Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show(); + } + } + + private void doPageUp() { + if (startRow >= pageSize && pageSize > 0) { + endRow = startRow - 1; + startRow -= pageSize; + new ShowPage().execute((Void[]) null); + } else { + String msg = "已经是第一页"; + Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show(); + } + } + + private int d2p(float dp) { + return (int) (dp * pixelPerDp + 0.5); + } + + private class PageData { + public Map headData; + public List>> rowData; + public List rowOnClick; + } + +} diff --git a/src/info/zhangxing/ZPagedHScrollTableDataAdapter.java b/src/info/zhangxing/ZPagedHScrollTableDataAdapter.java new file mode 100644 index 0000000..30d6e58 --- /dev/null +++ b/src/info/zhangxing/ZPagedHScrollTableDataAdapter.java @@ -0,0 +1,55 @@ +package info.zhangxing; + +import java.util.List; +import java.util.Map; + +import android.view.View.OnClickListener; + +/** + * 数据适配器接口,用于获取待展现的数据内容 + * + * @author open@zhangxing.info + * + */ +public interface ZPagedHScrollTableDataAdapter { + final static Integer FIXED_COLUMN = 0; + final static Integer SCROLLABLE_COLUMN = 1; + + /** + * 获取列标题列表 + * + * @return 一个 HashMap,其中 FIXED_COLUMN 为键的列表是固定列标题数据;SCROLLABLE_COLUMN + * 为键的列表是滑动列数据。两个键必须都存在,对应列类型没有需要展现的列时应取值空数组。 + */ + Map getColHeaders(); + + /** + * 获取总数据行数 + * + * @return 总数据行数 + */ + int getTotalRows(); + + /** + * 获取指定范围内的记录数据 + * + * @param startRow + * 起始行号,从 0 开始计算 + * @param endRow + * 结束行号,从 0 开始计算 + * @return 包含若干行记录的列表,每行记录是一个 HashMap,其中 FIXED_COLUMN + * 为键的列表是固定列数据,顺序同固定列标题的顺序;SCROLLABLE_COLUMN + * 为键的列表是滑动列数据,顺序同滑动列标题的顺序。若数据少于要求的行号范围,则仅返回实际数量的数据。无数据时应返回一个空 List。 + */ + List>> getRows(final int startRow, + final int endRow); + + /** + * 获取指定行对应的点击事件处理对象 + * + * @param row + * 在整个数据集中的行号,并非当前页内的行号,从 0 开始计算 + * @return 点击事件处理对象,不需要处理点击事件时应返回 null + */ + OnClickListener getOnClickListener(final int row); +} diff --git a/src/info/zhangxing/ZPagedHScrollTableHSV.java b/src/info/zhangxing/ZPagedHScrollTableHSV.java new file mode 100644 index 0000000..f29f7bb --- /dev/null +++ b/src/info/zhangxing/ZPagedHScrollTableHSV.java @@ -0,0 +1,76 @@ +package info.zhangxing; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.HorizontalScrollView; + +/** + * 自定义横向滚动视图,可自动控制左右滚动指示控件显示与否 + * + * @author open@zhangxing.info + * + */ +public class ZPagedHScrollTableHSV extends HorizontalScrollView { + + View leftIndicator; + View rightIndicator; + + public ZPagedHScrollTableHSV(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setLeftIndicator(View leftIndicator) { + this.leftIndicator = leftIndicator; + } + + public void setRightIndicator(View rightIndicator) { + this.rightIndicator = rightIndicator; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + if (leftIndicator != null) { + leftIndicator.setVisibility(INVISIBLE); + } + + if (rightIndicator != null) { + if (getChildAt(0).getMeasuredWidth() <= getWidth()) { + rightIndicator.setVisibility(INVISIBLE); + } else { + rightIndicator.setVisibility(VISIBLE); + } + } + super.onLayout(changed, l, t, r, b); + } + + @Override + protected void onScrollChanged(final int l, final int t, final int oldl, + final int oldt) { + if (leftIndicator != null) { + if (l == 0) { + leftIndicator.setVisibility(INVISIBLE); + } else { + leftIndicator.setVisibility(VISIBLE); + } + } + + /* + * 对于横向 ScrollView,判断滚动到最右侧的标准为 LinearLayout.getMeasuredWidth() <= l + + * getWidth() + * + * 对于纵向 ScrollView,判断滚动到最底部的标准为 LinearLayout.getMeasuredHeight() <= t + + * getHeight() + */ + if (rightIndicator != null) { + if (getChildAt(0).getMeasuredWidth() <= l + getWidth()) { + rightIndicator.setVisibility(INVISIBLE); + } else { + rightIndicator.setVisibility(VISIBLE); + } + } + + super.onScrollChanged(l, t, oldl, oldt); + } + +}