本文主要研究了一下如何把树形结构的数据保存到文件并读取出来。为了更形象说明用了一个界面程序显示,程序用了model/view框架。
数据类class DataItem { public: DataItem(int id = 100,QString name = "root"); ~DataItem(); void SetRoot(DataItem *root); void SerialzeData(bool isSave,QDataStream &stream); void Clear(); void Init(); //protected: int GetID() { return ID; } QString GetName() { return Name; } void SetID(int id) { ID = id; } void SetName(QString name) { Name = name; } int GetSize() { return dataVec.size(); } void AddItem(DataItem *pItem); void DeleteItem(DataItem *pItem); void DeleteItem(int index); DataItem *GetItem(int index); DataItem *GetParent() { return pRoot; } int indexOf(DataItem* pItem); private: int ID; QString Name; vector<DataItem*> dataVec; DataItem *pRoot; };
DataItem::DataItem( int id,QString name ):ID(id),Name(name),pRoot(NULL) { //pRoot = new DataItem(100,"Root"); } DataItem::~DataItem() { } //SerialzeData 原来是,保存数据时,先保存每个项的数据,在后面保存该项的子节点个数,并递归保存各个子节点数据 void DataItem::SerialzeData( bool isSave,QDataStream &stream ) { if (isSave) { stream<<GetID()<<GetName(); //save ID and Name stream<<dataVec.size(); //save the number of child for(int i = 0; i < dataVec.size(); ++i) { dataVec[i]->SerialzeData(isSave,stream); } } else { int id; int size; QString name; stream>>id>>name; //Get ID and Name SetID(id); SetName(name); stream>>size; //Get the number of child for(int i = 0; i < size; ++i) { DataItem *pItem = new DataItem(0,"name"); pItem->SerialzeData(isSave,stream); AddItem(pItem); } } } void DataItem::AddItem( DataItem *pItem ) { pItem->SetRoot(this); dataVec.push_back(pItem); } void DataItem::DeleteItem( DataItem *pItem ) { vector<DataItem*>::iterator it = dataVec.begin(); for (it; it != dataVec.end(); ++it) { if (*it == pItem) { dataVec.erase(it); break; } } } void DataItem::DeleteItem( int index ) { if (index < dataVec.size()) { vector<DataItem*>::iterator it = dataVec.begin(); it = it + index; dataVec.erase(it); } } void DataItem::Init() { for (int i = 0; i < 5; ++i) { DataItem *pItem = new DataItem(i,QString("child%1").arg(i)); pRoot->AddItem(pItem); for (int j = 0; j < 2; ++j) { DataItem *pChild = new DataItem(j,QString("grandchild%0 -%1").arg(i).arg(j)); pItem->AddItem(pChild); } } } void DataItem::SetRoot( DataItem *root ) { pRoot = root; } void DataItem::Clear() { dataVec.clear(); } DataItem * DataItem::GetItem( int index ) { if (index < dataVec.size()) { return dataVec[index]; } else { return NULL; } } int DataItem::indexOf( DataItem* pItem ) { int index = -1; for (int i = 0; i < dataVec.size(); ++i) { if (dataVec[i] == pItem) { index = i; break; } } return index; }
数据模型
class TreeDataModel:public QAbstractItemModel { Q_OBJECT public: TreeDataModel(QObject *parent = NULL); ~TreeDataModel(); void SetRoot(DataItem *pRoot) { m_pTreeData = pRoot; } QModelIndex parent ( const QModelIndex & index ) const; QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const ; QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const; int columnCount ( const QModelIndex & parent = QModelIndex() ) const; int rowCount ( const QModelIndex & parent = QModelIndex() ) const; DataItem* dataFromIndex(const QModelIndex &index) const; void SaveData(QDataStream &out); void LoadData(QDataStream &in); protected: private: DataItem *m_pTreeData; };
TreeDataModel::TreeDataModel( QObject *parent /*= NULL*/ ):QAbstractItemModel(parent) { m_pTreeData = NULL; } TreeDataModel::~TreeDataModel() { } QVariant TreeDataModel::data( const QModelIndex & index, int role /*= Qt::DisplayRole */ ) const { DataItem *pItem = dataFromIndex(index); if ((pItem)&&(role == Qt::DisplayRole)) { switch (index.column()) { case 0: return pItem->GetID(); case 1: return pItem->GetName(); } } return QVariant(); } QVariant TreeDataModel::headerData( int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole */ ) const { if ((section <2) && (orientation == Qt::Horizontal)&& (role == Qt::DisplayRole)) { switch (section) { case 0: return tr("编号"); case 1: return tr("名称"); default: return QVariant(); } } else { return QVariant(); } } QModelIndex TreeDataModel::index( int row, int column, const QModelIndex & parent /*= QModelIndex() */ ) const { if (!m_pTreeData ||row < 0 || column < 0) { return QModelIndex(); } else { DataItem *pItem = dataFromIndex(parent); if (pItem) { DataItem *pChild = pItem->GetItem(row); if (pChild) { return createIndex(row,column,pChild); } } return QModelIndex(); } } int TreeDataModel::columnCount( const QModelIndex & parent /*= QModelIndex() */ ) const { return 2; } int TreeDataModel::rowCount( const QModelIndex & parent /*= QModelIndex() */ ) const { DataItem *pItem = dataFromIndex(parent); if (pItem) { return pItem->GetSize(); } return 0; } DataItem* TreeDataModel::dataFromIndex( const QModelIndex &index ) const { if (index.isValid()) { return static_cast<DataItem*>(index.internalPointer()); } else { return m_pTreeData; //这里不要返回NULL } } QModelIndex TreeDataModel::parent( const QModelIndex & index ) const { if (index.isValid()) { DataItem *pItem = dataFromIndex(index); if (pItem) { DataItem *pParent = pItem->GetParent(); if (pParent) { DataItem *pGrandParent = pParent->GetParent(); if (pGrandParent) { int row = pGrandParent->indexOf(pParent); return createIndex(row,index.column(),pParent); } } } } return QModelIndex(); } void TreeDataModel::SaveData( QDataStream &out ) { m_pTreeData->SerialzeData(true,out); } void TreeDataModel::LoadData( QDataStream &in ) { m_pTreeData->SerialzeData(false,in); }
主框架类
class MainWidget:public QWidget { Q_OBJECT public: MainWidget(QWidget *patent = NULL); ~MainWidget(); protected slots: void leftSelectBtnSlot(); void rightSelectBtnSlot(); void saveBtnSlot(); void loadBtnSlot(); private: QSplitter *m_pSplitter; QTreeView *m_pLeftTreeView; QTreeView *m_pRightTreeView; QPushButton *m_pLeftSaveBtn; QPushButton *m_pRightLoadBtn; QPushButton *m_pLeftSelectBtn; QPushButton *m_pRightSelectBtn; QLineEdit *m_pLeftLEdit; QLineEdit *m_pRightLEdit; QGridLayout *m_pLeftLayout; QGridLayout *m_pRightLayout; TreeDataModel *m_pLeftModel; TreeDataModel *m_pRightModel; };
MainWidget::MainWidget( QWidget *patent /*= NULL*/ ):QWidget(patent) { m_pLeftModel = new TreeDataModel(); m_pRightModel = new TreeDataModel(); m_pSplitter = new QSplitter(this); QFrame *pLeftFrame = new QFrame(this); QFrame *pRightFrame = new QFrame(this); m_pLeftLayout = new QGridLayout(pLeftFrame); m_pRightLayout = new QGridLayout(pRightFrame); m_pLeftLEdit = new QLineEdit(this); m_pRightLEdit = new QLineEdit(this); m_pLeftSaveBtn = new QPushButton(tr("保存"),this); m_pRightLoadBtn = new QPushButton(tr("加载"),this); m_pLeftTreeView = new QTreeView(this); m_pRightTreeView = new QTreeView(this); m_pLeftSelectBtn = new QPushButton(tr("选择文件"),this); m_pRightSelectBtn = new QPushButton(tr("选择文件"),this); m_pRightLEdit->setReadOnly(true); m_pLeftLayout->addWidget(m_pLeftSelectBtn,0,0,1,1); m_pLeftLayout->addWidget(m_pLeftLEdit,0,1,1,1); m_pLeftLayout->addWidget(m_pLeftSaveBtn,0,2,1,1); m_pLeftLayout->addWidget(m_pLeftTreeView,1,0,3,3); m_pRightLayout->addWidget(m_pRightSelectBtn,0,0,1,1); m_pRightLayout->addWidget(m_pRightLEdit,0,1,1,1); m_pRightLayout->addWidget(m_pRightLoadBtn,0,2,1,1); m_pRightLayout->addWidget(m_pRightTreeView,1,0,3,3); m_pLeftTreeView->setModel(m_pLeftModel); m_pRightTreeView->setModel(m_pRightModel); DataItem *pTreeData = new DataItem(); pTreeData->SetRoot(pTreeData); pTreeData->Init(); m_pLeftModel->SetRoot(pTreeData); //m_pRightModel->SetRoot(pTreeData); m_pSplitter->addWidget(pLeftFrame); m_pSplitter->addWidget(pRightFrame); connect(m_pLeftSelectBtn,SIGNAL(clicked()),this,SLOT(leftSelectBtnSlot())); connect(m_pRightSelectBtn,SIGNAL(clicked()),this,SLOT(rightSelectBtnSlot())); connect(m_pLeftSaveBtn,SIGNAL(clicked()),this,SLOT(saveBtnSlot())); connect(m_pRightLoadBtn,SIGNAL(clicked()),this,SLOT(loadBtnSlot())); this->setFixedSize(QSize(650,250)); } MainWidget::~MainWidget() { } void MainWidget::leftSelectBtnSlot() //这里只是选择了一个文件夹路径,在保存之前还需要加文件名 { QFileDialog Dialog(this,tr("选择目录"),"",""); Dialog.setFileMode(QFileDialog::Directory); //Dialog.setNameFilter("*.data"); if (Dialog.exec()) { QStringList dirs = Dialog.selectedFiles(); if (dirs.size() > 0) { m_pLeftLEdit->setText(QDir::toNativeSeparators(dirs.at(0))); } } } void MainWidget::rightSelectBtnSlot() //选择之前保存的.data文件进行加载显示 { QFileDialog Dialog(this,tr("选择文件"),"",""); Dialog.setFileMode(QFileDialog::ExistingFile); Dialog.setNameFilter("*.data"); if (Dialog.exec()) { QStringList files = Dialog.selectedFiles(); if (files.size() > 0) { m_pRightLEdit->setText(QDir::toNativeSeparators(files.at(0))); } } } void MainWidget::saveBtnSlot() {
QString filePath = m_pLeftLEdit->text(); if ((filePath.isEmpty()) || filePath.endsWith("\\") || filePath.endsWith("/")) //必须得添加文件名,文件名规定后缀为.data { QMessageBox::information(this,tr("提示"),tr("请输入文件名"),QMessageBox::Ok); return; } else if(filePath.endsWith("data")) { QFile file(filePath); if (file.open(QIODevice::WriteOnly)) { QDataStream outStream(&file); m_pLeftModel->SaveData(outStream); } } } void MainWidget::loadBtnSlot() { QString filePath = m_pRightLEdit->text(); if((!filePath.isEmpty()) &&filePath.endsWith("data")) { DataItem *pTreeData = new DataItem(); //pTreeData->SetRoot(pTreeData); m_pRightModel->SetRoot(pTreeData); QFile file(filePath); if (file.open(QIODevice::ReadOnly)) { QDataStream inStream(&file); m_pRightModel->LoadData(inStream); m_pRightTreeView->setModel(m_pRightModel); m_pRightTreeView->reset(); //必须的,不然不会刷新 } } }
运行结果如下图
EGOTableViewPullRefresh 是fork EGOTableViewPullRefresh开源类库进行的改进,添加了上提加载更多效果。同时也可以通过一个按钮的触发刷新事件,但是刷新的时候不能跳到top,为了动态展示,再刷新的时候按钮旋转,然后跳转回到顶部!如下如图
关于EGOTableViewPullRefresh可以参照http://blog.csdn.net/duxinfeng2010/article/details/9007311,翻译过的用法,在这个Demo基础上进行修改,点击Demo下载;
1、给工程添加一个导航栏,在application: didFinishLaunchingWithOptions:方法中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. // [[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"navbar.png"] forBarMetrics:UIBarMetricsDefault]; UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.viewController]; self.window.rootViewController = nav; [self.window makeKeyAndVisible]; return YES; }
2、在ViewDidLoad方法中,修改背景图片,添加刷新按钮
- (void)viewDidLoad { [super viewDidLoad]; self.navigationController.navigationBar.tintColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"navbar.png"]]; self.pullTableView.pullArrowImage = [UIImage imageNamed:@"blackArrow"]; // self.pullTableView.pullBackgroundColor = [UIColor yellowColor]; self.pullTableView.pullTextColor = [UIColor blackColor]; CGRect rect = CGRectMake(0, 0, 44, 44); UIButton *refreshBtn = [UIButton buttonWithType:UIButtonTypeRoundedRect]; refreshBtn.frame = rect; [refreshBtn setBackgroundImage:[UIImage imageNamed:@"button_refresh"] forState:UIControlStateNormal]; [refreshBtn addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventTouchUpInside]; UIBarButtonItem *refreshItem = [[UIBarButtonItem alloc] initWithCustomView:refreshBtn]; self.navigationItem.leftBarButtonItem = refreshItem; }
3、添加刷新按钮事件,和按钮旋转方法
//按钮旋转 - (void)startAnimation:(UIButton *)button{ CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; [rotate setByValue:[NSNumber numberWithFloat:M_PI*4]]; rotate.duration = 3.0; rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; [button.layer addAnimation:rotate forKey:@"myRotationAnimation"]; }
-(void)refresh:(UIButton *)button { [self startAnimation:button]; // 判断一下table是否处于刷新状态,如果没有则执行本次刷新 if (!self.pullTableView.pullTableIsRefreshing) { self.pullTableView.pullTableIsRefreshing = YES; // 设置回到top时候table的位置 [self.pullTableView setContentOffset:CGPointMake(0, -60) animated:YES]; [self performSelector:@selector(refreshTable) withObject:nil afterDelay:3.0]; } }
源码下载地址:https://github.com/XFZLDXF/RefreshButtonDemo
在开发的过程当中,由于手机屏幕的大小的限制,我们经常需要使用滑动的方式,来显示更多的内容。在最近的工作中,遇见一个需求,需要将ListView嵌套到ScrollView中显示。于是乎有了如下布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFE1FF" android:orientation="vertical" > <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" > <ListView android:id="@+id/listView1" android:layout_width="match_parent" android:layout_height="match_parent" android:fadingEdge="vertical" android:fadingEdgeLength="5dp" /> </LinearLayout> </ScrollView> </LinearLayout>
运行程序,如下结果,无论你如何调整layout_width,layout_height属性,ListView列表只显示一列!
在查阅的各种文档和资料后,发现在ScrollView中嵌套ListView空间,无法正确的计算ListView的大小,故可以通过代码,根据当前的ListView的列表项计算列表的尺寸。实现代码如下:
public class MainActivity extends Activity { private ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.listView1); String[] adapterData = new String[] { "Afghanistan", "Albania",… … "Bosnia"}; listView.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,adapterData)); setListViewHeightBasedOnChildren(listView); } public void setListViewHeightBasedOnChildren(ListView listView) { // 获取ListView对应的Adapter ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { return; } int totalHeight = 0; for (int i = 0, len = listAdapter.getCount(); i < len; i++) { // listAdapter.getCount()返回数据项的数目 View listItem = listAdapter.getView(i, null, listView); // 计算子项View 的宽高 listItem.measure(0, 0); // 统计所有子项的总高度 totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight+ (listView.getDividerHeight() * (listAdapter.getCount() - 1)); // listView.getDividerHeight()获取子项间分隔符占用的高度 // params.height最后得到整个ListView完整显示需要的高度 listView.setLayoutParams(params); } }运行结果,OK问题搞定,打完收工!