1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-29 11:10:35 +02:00

Replace list view with recycler view

Makes it possible to keep all measurements in a single list. Also
added year dividers to break up the long list visually.
This commit is contained in:
Erik Johansson
2018-06-20 21:36:34 +02:00
3 changed files with 148 additions and 259 deletions

View File

@@ -42,6 +42,7 @@ dependencies {
implementation "com.android.support:design:${supportLibVersion}" implementation "com.android.support:design:${supportLibVersion}"
implementation "com.android.support:support-v4:${supportLibVersion}" implementation "com.android.support:support-v4:${supportLibVersion}"
implementation "com.android.support:appcompat-v7:${supportLibVersion}" implementation "com.android.support:appcompat-v7:${supportLibVersion}"
implementation "com.android.support:recyclerview-v7:${supportLibVersion}"
// HelloCharts // HelloCharts
implementation 'com.github.lecho:hellocharts-library:1.5.8@aar' implementation 'com.github.lecho:hellocharts-library:1.5.8@aar'

View File

@@ -17,24 +17,18 @@ package com.health.openscale.gui.fragments;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat; import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TableLayout;
import android.widget.TableRow; import android.widget.TableRow;
import android.widget.TextView; import android.widget.TextView;
@@ -45,45 +39,43 @@ import com.health.openscale.gui.activities.DataEntryActivity;
import com.health.openscale.gui.views.MeasurementView; import com.health.openscale.gui.views.MeasurementView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.Calendar;
import java.util.Date;
import java.util.List; import java.util.List;
import static android.util.TypedValue.COMPLEX_UNIT_DIP; import static android.util.TypedValue.COMPLEX_UNIT_DIP;
public class TableFragment extends Fragment implements FragmentUpdateListener { public class TableFragment extends Fragment implements FragmentUpdateListener {
private View tableView; private View tableView;
private ListView tableDataView;
private LinearLayout tableHeaderView; private LinearLayout tableHeaderView;
private LinearLayout subpageView;
private RecyclerView recyclerView;
private MeasurementsAdapter adapter;
private LinearLayoutManager layoutManager;
private List<MeasurementView> measurementViews; private List<MeasurementView> measurementViews;
private int selectedSubpageNr;
private static final String SELECTED_SUBPAGE_NR_KEY = "selectedSubpageNr";
public TableFragment() { public TableFragment() {
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
{
tableView = inflater.inflate(R.layout.fragment_table, container, false); tableView = inflater.inflate(R.layout.fragment_table, container, false);
subpageView = tableView.findViewById(R.id.subpageView);
tableDataView = tableView.findViewById(R.id.tableDataView);
tableHeaderView = tableView.findViewById(R.id.tableHeaderView); tableHeaderView = tableView.findViewById(R.id.tableHeaderView);
recyclerView = tableView.findViewById(R.id.tableDataView);
tableDataView.setAdapter(new ListViewAdapter()); recyclerView.setHasFixedSize(true);
tableDataView.setOnItemClickListener(new onClickListenerRow());
if (savedInstanceState == null) { layoutManager = new LinearLayoutManager(getContext());
selectedSubpageNr = 0; recyclerView.setLayoutManager(layoutManager);
}
else { recyclerView.addItemDecoration(new DividerItemDecoration(
selectedSubpageNr = savedInstanceState.getInt(SELECTED_SUBPAGE_NR_KEY); recyclerView.getContext(), layoutManager.getOrientation()));
}
adapter = new MeasurementsAdapter();
recyclerView.setAdapter(adapter);
measurementViews = MeasurementView.getMeasurementList( measurementViews = MeasurementView.getMeasurementList(
getContext(), MeasurementView.DateTimeOrder.FIRST); getContext(), MeasurementView.DateTimeOrder.FIRST);
@@ -103,236 +95,176 @@ public class TableFragment extends Fragment implements FragmentUpdateListener {
super.onDestroyView(); super.onDestroyView();
} }
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_SUBPAGE_NR_KEY, selectedSubpageNr);
}
@Override @Override
public void updateOnView(List<ScaleMeasurement> scaleMeasurementList) public void updateOnView(List<ScaleMeasurement> scaleMeasurementList)
{ {
final int maxSize = 25;
final int subpageCount = (int)Math.ceil(scaleMeasurementList.size() / (double)maxSize);
if (selectedSubpageNr >= subpageCount) {
selectedSubpageNr = Math.max(0, subpageCount - 1);
}
subpageView.removeAllViews();
Button moveSubpageLeft = new Button(tableView.getContext());
moveSubpageLeft.setText("<");
moveSubpageLeft.setPadding(0,0,0,0);
moveSubpageLeft.setTextColor(Color.WHITE);
moveSubpageLeft.setBackground(ContextCompat.getDrawable(tableView.getContext(), R.drawable.flat_selector));
moveSubpageLeft.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
moveSubpageLeft.getLayoutParams().height = pxImageDp(20);
moveSubpageLeft.getLayoutParams().width = pxImageDp(50);
moveSubpageLeft.setOnClickListener(new onClickListenerMoveSubpageLeft());
moveSubpageLeft.setEnabled(selectedSubpageNr > 0);
subpageView.addView(moveSubpageLeft);
for (int i = 0; i < subpageCount; i++) {
TextView subpageNrView = new TextView(tableView.getContext());
subpageNrView.setOnClickListener(new onClickListenerSubpageSelect());
subpageNrView.setText(Integer.toString(i+1));
subpageNrView.setTextColor(Color.GRAY);
subpageNrView.setPadding(10, 10, 20, 10);
subpageView.addView(subpageNrView);
}
if (subpageView.getChildCount() > 1) {
TextView selectedSubpageNrView = (TextView) subpageView.getChildAt(selectedSubpageNr + 1);
if (selectedSubpageNrView != null) {
selectedSubpageNrView.setTypeface(null, Typeface.BOLD);
selectedSubpageNrView.setTextColor(Color.WHITE);
}
}
Button moveSubpageRight = new Button(tableView.getContext());
moveSubpageRight.setText(">");
moveSubpageRight.setPadding(0,0,0,0);
moveSubpageRight.setTextColor(Color.WHITE);
moveSubpageRight.setBackground(ContextCompat.getDrawable(tableView.getContext(), R.drawable.flat_selector));
moveSubpageRight.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
moveSubpageRight.getLayoutParams().height = pxImageDp(20);
moveSubpageRight.getLayoutParams().width = pxImageDp(50);
moveSubpageRight.setOnClickListener(new onClickListenerMoveSubpageRight());
moveSubpageRight.setEnabled(selectedSubpageNr + 1 < subpageCount);
subpageView.addView(moveSubpageRight);
subpageView.setVisibility(subpageCount > 1 ? View.VISIBLE : View.GONE);
tableHeaderView.removeAllViews(); tableHeaderView.removeAllViews();
final int iconHeight = pxImageDp(20);
ArrayList<MeasurementView> visibleMeasurements = new ArrayList<>(); ArrayList<MeasurementView> visibleMeasurements = new ArrayList<>();
for (MeasurementView measurement : measurementViews) { for (MeasurementView measurement : measurementViews) {
if (!measurement.isVisible()) {
if (measurement.isVisible()) { continue;
ImageView headerIcon = new ImageView(tableView.getContext());
headerIcon.setImageDrawable(measurement.getIcon());
headerIcon.setLayoutParams(new TableRow.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT, TableLayout.LayoutParams.MATCH_PARENT, 1));
headerIcon.getLayoutParams().width = 0;
headerIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
headerIcon.getLayoutParams().height = pxImageDp(20);
tableHeaderView.addView(headerIcon);
visibleMeasurements.add(measurement);
} }
ImageView headerIcon = new ImageView(tableView.getContext());
headerIcon.setImageDrawable(measurement.getIcon());
headerIcon.setLayoutParams(new TableRow.LayoutParams(0, iconHeight, 1));
headerIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
tableHeaderView.addView(headerIcon);
visibleMeasurements.add(measurement);
} }
ListViewAdapter adapter = (ListViewAdapter) tableDataView.getAdapter(); adapter.setMeasurements(visibleMeasurements, scaleMeasurementList);
final int startOffset = maxSize * selectedSubpageNr;
final int endOffset = Math.min(startOffset + maxSize + 1, scaleMeasurementList.size());
adapter.setMeasurements(visibleMeasurements, scaleMeasurementList.subList(startOffset, endOffset), maxSize);
} }
private int pxImageDp(float dp) { private int pxImageDp(float dp) {
return (int)(dp * getResources().getDisplayMetrics().density + 0.5f); return (int)(dp * getResources().getDisplayMetrics().density + 0.5f);
} }
private class onClickListenerRow implements AdapterView.OnItemClickListener { private class MeasurementsAdapter extends RecyclerView.Adapter<MeasurementsAdapter.ViewHolder> {
@Override public static final int VIEW_TYPE_MEASUREMENT = 0;
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public static final int VIEW_TYPE_YEAR = 1;
Intent intent = new Intent(tableView.getContext(), DataEntryActivity.class);
intent.putExtra(DataEntryActivity.EXTRA_ID, (int)id);
startActivity(intent);
}
}
private class onClickListenerMoveSubpageLeft implements View.OnClickListener { public class ViewHolder extends RecyclerView.ViewHolder {
@Override public LinearLayout measurementView;
public void onClick(View v) { public ViewHolder(LinearLayout view) {
if (selectedSubpageNr > 0) { super(view);
selectedSubpageNr--; measurementView = view;
updateOnView(OpenScale.getInstance().getScaleMeasurementList());
} }
} }
}
private class onClickListenerMoveSubpageRight implements View.OnClickListener {
@Override
public void onClick(View v) {
if (selectedSubpageNr < (subpageView.getChildCount() - 3)) {
selectedSubpageNr++;
updateOnView(OpenScale.getInstance().getScaleMeasurementList());
}
}
}
private class onClickListenerSubpageSelect implements View.OnClickListener {
@Override
public void onClick(View v) {
TextView nrView = (TextView)v;
selectedSubpageNr = Integer.parseInt(nrView.getText().toString())-1;
updateOnView(OpenScale.getInstance().getScaleMeasurementList());
}
}
private class ListViewAdapter extends BaseAdapter {
private List<MeasurementView> visibleMeasurements; private List<MeasurementView> visibleMeasurements;
private List<ScaleMeasurement> scaleMeasurements; private List<ScaleMeasurement> scaleMeasurements;
private int measurementsToShow = 0;
private Spanned[][] stringCache;
public void setMeasurements(List<MeasurementView> visibleMeasurements, public void setMeasurements(List<MeasurementView> visibleMeasurements,
List<ScaleMeasurement> scaleMeasurements, List<ScaleMeasurement> scaleMeasurements) {
int maxSize) {
this.visibleMeasurements = visibleMeasurements; this.visibleMeasurements = visibleMeasurements;
this.scaleMeasurements = scaleMeasurements; this.scaleMeasurements = new ArrayList<>(scaleMeasurements.size() + 10);
measurementsToShow = Math.min(scaleMeasurements.size(), maxSize);
stringCache = new Spanned[measurementsToShow][visibleMeasurements.size()]; Calendar calendar = Calendar.getInstance();
if (!scaleMeasurements.isEmpty()) {
calendar.setTime(scaleMeasurements.get(0).getDateTime());
}
calendar.set(calendar.get(Calendar.YEAR), 0, 1, 0, 0, 0);
calendar.set(calendar.MILLISECOND, 0);
// Copy all measurements from input parameter to member variable and insert
// an extra "null" entry when the year changes.
Date yearStart = calendar.getTime();
for (int i = 0; i < scaleMeasurements.size(); ++i) {
final ScaleMeasurement measurement = scaleMeasurements.get(i);
if (measurement.getDateTime().before(yearStart)) {
this.scaleMeasurements.add(null);
Calendar newCalendar = Calendar.getInstance();
newCalendar.setTime(measurement.getDateTime());
calendar.set(Calendar.YEAR, newCalendar.get(Calendar.YEAR));
yearStart = calendar.getTime();
}
this.scaleMeasurements.add(measurement);
}
notifyDataSetChanged(); notifyDataSetChanged();
} }
@Override @Override
public int getCount() { public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return measurementsToShow; LinearLayout row = new LinearLayout(getContext());
} row.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
@Override final int screenSize = getResources().getConfiguration()
public Object getItem(int position) { .screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
return scaleMeasurements.get(position); final boolean isSmallScreen =
} screenSize != Configuration.SCREENLAYOUT_SIZE_XLARGE
&& screenSize != Configuration.SCREENLAYOUT_SIZE_LARGE;
@Override final int count = viewType == VIEW_TYPE_YEAR ? 1 : visibleMeasurements.size();
public long getItemId(int position) { for (int i = 0; i < count; ++i) {
return scaleMeasurements.get(position).getId(); TextView column = new TextView(getContext());
} column.setLayoutParams(new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
@Override if (viewType == VIEW_TYPE_MEASUREMENT) {
public View getView(int position, View convertView, ViewGroup parent) {
// Create entries in stringCache if needed
if (stringCache[position][0] == null) {
ScaleMeasurement measurement = scaleMeasurements.get(position);
ScaleMeasurement prevMeasurement = null;
if (position + 1 < scaleMeasurements.size()) {
prevMeasurement = scaleMeasurements.get(position + 1);
}
for (int i = 0; i < visibleMeasurements.size(); ++i) {
visibleMeasurements.get(i).loadFrom(measurement, prevMeasurement);
SpannableStringBuilder string = new SpannableStringBuilder();
string.append(visibleMeasurements.get(i).getValueAsString(false));
visibleMeasurements.get(i).appendDiffValue(string, true);
stringCache[position][i] = string;
}
}
// Create view if needed
LinearLayout row;
if (convertView == null) {
row = new LinearLayout(getContext());
final int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
final boolean isSmallScreen =
screenSize != Configuration.SCREENLAYOUT_SIZE_XLARGE
&& screenSize != Configuration.SCREENLAYOUT_SIZE_LARGE;
for (int i = 0; i < visibleMeasurements.size(); ++i) {
TextView column = new TextView(getContext());
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
1);
layoutParams.width = 0;
column.setLayoutParams(layoutParams);
column.setMinLines(2); column.setMinLines(2);
column.setGravity(Gravity.CENTER_HORIZONTAL); column.setGravity(Gravity.CENTER_HORIZONTAL);
if (isSmallScreen) { if (isSmallScreen) {
column.setTextSize(COMPLEX_UNIT_DIP, 9); column.setTextSize(COMPLEX_UNIT_DIP, 9);
} }
row.addView(column);
} }
else {
column.setPadding(0, 10, 0, 10);
column.setGravity(Gravity.CENTER);
column.setTextSize(COMPLEX_UNIT_DIP, 16);
}
row.addView(column);
} }
else {
row = (LinearLayout) convertView; return new ViewHolder(row);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
LinearLayout row = holder.measurementView;
final ScaleMeasurement measurement = scaleMeasurements.get(position);
if (measurement == null) {
ScaleMeasurement nextMeasurement = scaleMeasurements.get(position + 1);
Calendar calendar = Calendar.getInstance();
calendar.setTime(nextMeasurement.getDateTime());
TextView column = (TextView) row.getChildAt(0);
column.setText(String.format("%d", calendar.get(Calendar.YEAR)));
return;
}
ScaleMeasurement prevMeasurement = null;
if (position + 1 < scaleMeasurements.size()) {
prevMeasurement = scaleMeasurements.get(position + 1);
if (prevMeasurement == null) {
prevMeasurement = scaleMeasurements.get(position + 2);
}
} }
// Fill view with data // Fill view with data
for (int i = 0; i < visibleMeasurements.size(); ++i) { for (int i = 0; i < visibleMeasurements.size(); ++i) {
final MeasurementView view = visibleMeasurements.get(i);
view.loadFrom(measurement, prevMeasurement);
SpannableStringBuilder string = new SpannableStringBuilder();
string.append(view.getValueAsString(false));
view.appendDiffValue(string, true);
TextView column = (TextView) row.getChildAt(i); TextView column = (TextView) row.getChildAt(i);
column.setText(stringCache[position][i]); column.setText(string);
} }
return row; row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getContext(), DataEntryActivity.class);
intent.putExtra(DataEntryActivity.EXTRA_ID, measurement.getId());
startActivity(intent);
}
});
} }
@Override @Override
public boolean hasStableIds() { public int getItemCount() {
return true; return scaleMeasurements == null ? 0 : scaleMeasurements.size();
}
@Override
public int getItemViewType(int position) {
return scaleMeasurements.get(position) != null ? VIEW_TYPE_MEASUREMENT : VIEW_TYPE_YEAR;
} }
} }
} }

View File

@@ -1,68 +1,24 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:gravity="center"
android:background="?attr/colorPrimary"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:gravity="left"
android:layout_weight="1"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:id="@+id/subpageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:gravity="center"
android:layout_weight="0"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:gravity="right"
android:layout_weight="0.9"
android:orientation="horizontal">
</LinearLayout>
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/tableHeaderView" android:id="@+id/tableHeaderView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingBottom="5dp" android:paddingBottom="5dp"
android:paddingTop="5dp"> android:paddingTop="5dp" />
</LinearLayout>
<LinearLayout <View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="?android:attr/listDivider" />
<android.support.v7.widget.RecyclerView
android:id="@+id/tableDataView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="0.9" android:scrollbars="vertical" />
android:gravity="bottom"
android:orientation="horizontal">
<ListView
android:id="@+id/tableDataView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="5dp">
</ListView>
</LinearLayout>
</LinearLayout> </LinearLayout>