1
0
Fork 0
mirror of https://github.com/MatomoCamp/matomocamp-companion-android.git synced 2024-09-19 16:13:46 +02:00

Replaced the ListView main menu with a ScrollView and an AdapterLinearLayout.

It makes the layout simpler and fixes focus issues in keypad navigation mode.
This commit is contained in:
Christophe Beyls 2015-06-24 13:17:58 +02:00
parent 0c2ddc73e0
commit b6cc5513db
8 changed files with 242 additions and 99 deletions

View file

@ -43,10 +43,8 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
@ -62,13 +60,14 @@ import be.digitalia.fosdem.fragments.LiveFragment;
import be.digitalia.fosdem.fragments.MapFragment;
import be.digitalia.fosdem.fragments.PersonsListFragment;
import be.digitalia.fosdem.fragments.TracksFragment;
import be.digitalia.fosdem.widgets.AdapterLinearLayout;
/**
* Main entry point of the application. Allows to switch between section fragments and update the database.
*
* @author Christophe Beyls
*/
public class MainActivity extends ActionBarActivity implements ListView.OnItemClickListener {
public class MainActivity extends ActionBarActivity {
private enum Section {
TRACKS(TracksFragment.class, R.string.menu_tracks, R.drawable.ic_event_grey600_24dp, true),
@ -225,23 +224,16 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
// Setup Main menu
mainMenu = findViewById(R.id.main_menu);
ListView menuListView = (ListView) findViewById(R.id.main_menu_list);
LayoutInflater inflater = LayoutInflater.from(this);
View menuHeaderView = inflater.inflate(R.layout.header_main_menu, null);
menuListView.addHeaderView(menuHeaderView, null, false);
View menuFooterView = inflater.inflate(R.layout.footer_main_menu, null);
menuFooterView.findViewById(R.id.settings).setOnClickListener(menuFooterClickListener);
menuFooterView.findViewById(R.id.about).setOnClickListener(menuFooterClickListener);
menuListView.addFooterView(menuFooterView, null, false);
final AdapterLinearLayout sectionsList = (AdapterLinearLayout) findViewById(R.id.sections);
menuAdapter = new MainMenuAdapter(getLayoutInflater());
sectionsList.setAdapter(menuAdapter);
mainMenu.findViewById(R.id.settings).setOnClickListener(menuFooterClickListener);
mainMenu.findViewById(R.id.about).setOnClickListener(menuFooterClickListener);
LocalBroadcastManager.getInstance(this).registerReceiver(scheduleRefreshedReceiver, new IntentFilter(DatabaseManager.ACTION_SCHEDULE_REFRESHED));
menuAdapter = new MainMenuAdapter(inflater);
menuListView.setAdapter(menuAdapter);
menuListView.setOnItemClickListener(this);
// Last update date, below the menu
lastUpdateTextView = (TextView) findViewById(R.id.last_update);
// Last update date, below the list
lastUpdateTextView = (TextView) mainMenu.findViewById(R.id.last_update);
updateLastUpdateTime();
// Restore current section
@ -254,7 +246,18 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
currentSection = Section.values()[savedInstanceState.getInt(STATE_CURRENT_SECTION)];
}
// Ensure the current section is visible in the menu
menuListView.setSelection(currentSection.ordinal());
sectionsList.post(new Runnable() {
@Override
public void run() {
if (sectionsList.getChildCount() > currentSection.ordinal()) {
ScrollView mainMenuScrollView = (ScrollView) findViewById(R.id.main_menu_scroll);
int requiredScroll = sectionsList.getTop()
+ sectionsList.getChildAt(currentSection.ordinal()).getBottom()
- mainMenuScrollView.getHeight();
mainMenuScrollView.scrollTo(0, Math.max(0, requiredScroll));
}
}
});
updateActionBar();
}
@ -452,12 +455,11 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
}
};
private class MainMenuAdapter extends BaseAdapter {
private class MainMenuAdapter extends AdapterLinearLayout.Adapter<Section> {
private final Section[] sections = Section.values();
private final LayoutInflater inflater;
private final int currentSectionForegroundColor;
private final int currentSectionBackgroundColor;
public MainMenuAdapter(LayoutInflater inflater) {
this.inflater = inflater;
@ -468,7 +470,6 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
} finally {
a.recycle();
}
currentSectionBackgroundColor = getResources().getColor(R.color.translucent_grey);
}
@Override
@ -481,72 +482,65 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
return sections[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_main_menu, parent, false);
convertView.setOnClickListener(sectionClickListener);
}
Section section = getItem(position);
convertView.setSelected(section == currentSection);
TextView tv = (TextView) convertView.findViewById(R.id.section_text);
SpannableString sectionTitle = new SpannableString(getString(section.getTitleResId()));
Drawable sectionIcon = getResources().getDrawable(section.getIconResId());
int backgroundColor;
if (section == currentSection) {
// Special color for the current section
//sectionTitle.setSpan(new StyleSpan(Typeface.BOLD), 0, sectionTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sectionTitle.setSpan(new ForegroundColorSpan(currentSectionForegroundColor), 0, sectionTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// We need to mutate the drawable before applying the ColorFilter, or else all the similar drawable instances will be tinted.
sectionIcon.mutate().setColorFilter(currentSectionForegroundColor, PorterDuff.Mode.SRC_IN);
backgroundColor = currentSectionBackgroundColor;
} else {
backgroundColor = Color.TRANSPARENT;
}
tv.setText(sectionTitle);
tv.setCompoundDrawablesWithIntrinsicBounds(sectionIcon, null, null, null);
tv.setBackgroundColor(backgroundColor);
return convertView;
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Decrease position by 1 since the listView has a header view.
Section section = menuAdapter.getItem(position - 1);
if (section != currentSection) {
// Switch to new section
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fragment f = fm.findFragmentById(R.id.content);
if (f != null) {
if (currentSection.shouldKeep()) {
ft.detach(f);
} else {
ft.remove(f);
private final View.OnClickListener sectionClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
Section section = menuAdapter.getItem(((ViewGroup) view.getParent()).indexOfChild(view));
if (section != currentSection) {
// Switch to new section
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fragment f = fm.findFragmentById(R.id.content);
if (f != null) {
if (currentSection.shouldKeep()) {
ft.detach(f);
} else {
ft.remove(f);
}
}
}
String fragmentClassName = section.getFragmentClassName();
if (section.shouldKeep() && ((f = fm.findFragmentByTag(fragmentClassName)) != null)) {
ft.attach(f);
} else {
f = Fragment.instantiate(this, fragmentClassName);
ft.add(R.id.content, f, fragmentClassName);
}
ft.commit();
String fragmentClassName = section.getFragmentClassName();
if (section.shouldKeep() && ((f = fm.findFragmentByTag(fragmentClassName)) != null)) {
ft.attach(f);
} else {
f = Fragment.instantiate(MainActivity.this, fragmentClassName);
ft.add(R.id.content, f, fragmentClassName);
}
ft.commit();
currentSection = section;
menuAdapter.notifyDataSetChanged();
currentSection = section;
menuAdapter.notifyDataSetChanged();
}
drawerLayout.closeDrawer(mainMenu);
}
drawerLayout.closeDrawer(mainMenu);
}
};
public static class AboutDialogFragment extends DialogFragment {

View file

@ -0,0 +1,123 @@
package be.digitalia.fosdem.widgets;
import android.content.Context;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
/**
* Vertical LinearLayout populated by a special adapter.
*
* @author Christophe Beyls
*/
public class AdapterLinearLayout extends LinearLayout {
/**
* Implement this Adapter to populate the layout.
* Call notifyDataSetChanged() to update it.
*/
public static abstract class Adapter<T> {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
private void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
private void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public abstract int getCount();
public abstract T getItem(int position);
public abstract View getView(int position, View convertView, ViewGroup parent);
}
private class AdapterLinearLayoutDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
populateFromAdapter();
}
}
private Adapter mAdapter;
private AdapterLinearLayoutDataSetObserver mDataSetObserver;
public AdapterLinearLayout(Context context) {
this(context, null);
}
public AdapterLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
}
public void setAdapter(Adapter adapter) {
if (mAdapter == adapter) {
return;
}
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
removeAllViews();
mAdapter = adapter;
if (adapter != null && mDataSetObserver != null) {
populateFromAdapter();
adapter.registerDataSetObserver(mDataSetObserver);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mDataSetObserver = new AdapterLinearLayoutDataSetObserver();
if (mAdapter != null) {
populateFromAdapter();
mAdapter.registerDataSetObserver(mDataSetObserver);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
mDataSetObserver = null;
}
private void populateFromAdapter() {
final Adapter adapter = mAdapter;
final int currentCount = getChildCount();
final int newCount = adapter.getCount();
final int commonCount = Math.min(currentCount, newCount);
// 1. Update common views
for (int i = 0; i < commonCount; ++i) {
final View currentView = getChildAt(i);
final View newView = adapter.getView(i, currentView, this);
if (currentView != newView) {
// Edge case: View is not recycled
removeViewAt(i);
addView(newView, i);
}
}
// 2a. Add missing views
for (int i = commonCount; i < newCount; ++i) {
addView(adapter.getView(i, null, this));
}
// 2b. Remove extra views (starting from the end to avoid array copies)
for (int i = currentCount - 1; i >= commonCount; --i) {
removeViewAt(i);
}
}
}

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<selector>
<item
android:drawable="@color/translucent_grey"
android:state_selected="true"/>
<item android:drawable="@android:color/transparent"/>
</selector>
</item>
<!-- Usage of theme attributes in an XML drawable is only allowed in API 21+ -->
<item android:drawable="?attr/selectableItemBackground"/>
</layer-list>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<selector>
<item
android:drawable="@color/translucent_grey"
android:state_selected="true"/>
<item android:drawable="@android:color/transparent"/>
</selector>
</item>
<item android:drawable="@drawable/abc_item_background_holo_light"/>
</layer-list>

View file

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
style="@style/SeparatorLine"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"/>
<TextView
android:id="@+id/settings"
style="@style/MainMenuItem"
android:background="?selectableItemBackground"
android:text="@string/settings"/>
<TextView
android:id="@+id/about"
style="@style/MainMenuItem"
android:background="?selectableItemBackground"
android:text="@string/about"/>
</LinearLayout>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/main_menu_padding"
android:scaleType="center"
android:src="@drawable/fosdem_title"/>

View file

@ -35,19 +35,49 @@
android:layout_width="260dp"
android:layout_height="match_parent"
android:layout_gravity="left"
android:background="@color/main_menu_background"
android:orientation="vertical">
<ListView
android:id="@+id/main_menu_list"
<ScrollView
android:id="@+id/main_menu_scroll"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:cacheColorHint="@android:color/transparent"
android:divider="@null"
android:dividerHeight="0dp"
tools:listheader="@layout/header_main_menu"
tools:listitem="@layout/item_main_menu"/>
android:background="@color/main_menu_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/main_menu_padding"
android:scaleType="center"
android:src="@drawable/fosdem_title"/>
<be.digitalia.fosdem.widgets.AdapterLinearLayout
android:id="@+id/sections"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<View
style="@style/SeparatorLine"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"/>
<TextView
android:id="@+id/settings"
style="@style/MainMenuItem"
android:text="@string/settings"/>
<TextView
android:id="@+id/about"
style="@style/MainMenuItem"
android:text="@string/about"/>
</LinearLayout>
</ScrollView>
<TextView
android:id="@+id/last_update"

View file

@ -61,6 +61,8 @@
<!-- Styles -->
<style name="MainMenuItem" parent="TextAppearance.AppCompat.Body2">
<item name="android:background">@drawable/menu_item_background</item>
<item name="android:focusable">true</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/main_menu_item_height</item>
<item name="android:gravity">left|center_vertical</item>