├── res ├── drawable │ ├── icon.png │ └── accessory_disclosure.xml ├── drawable-hdpi │ ├── accessory_disclosure_white.png │ └── accessory_disclosure_default.png ├── drawable-ldpi │ ├── accessory_disclosure_white.png │ └── accessory_disclosure_default.png ├── drawable-mdpi │ ├── accessory_disclosure_white.png │ └── accessory_disclosure_default.png ├── values │ ├── strings.xml │ ├── styles.xml │ └── colors.xml ├── color │ └── text_color_selector.xml └── layout │ ├── table_header.xml │ └── table_cell.xml ├── src └── fr │ └── days │ └── android │ └── uitableview │ ├── model │ ├── AccessoryType.java │ ├── UITableItem.java │ ├── UITableHeaderItem.java │ ├── UITableCellItem.java │ └── IndexPath.java │ ├── listener │ ├── OnCellClickListener.java │ ├── OnHeaderClickListener.java │ ├── OnCellLongClickListener.java │ ├── OnHeaderLongClickListener.java │ └── OnCellAccessoryClickListener.java │ ├── adapter │ ├── UITableViewInternalAccessoryListener.java │ ├── UITableViewAdapter.java │ └── UITableViewAdapterInternal.java │ ├── view │ ├── UITableItemView.java │ ├── UITableHeaderView.java │ ├── UITableView.java │ └── UITableCellView.java │ └── drawable │ └── UITableCellDrawable.java ├── .gitignore ├── AndroidManifest.xml ├── project.properties ├── proguard-project.txt ├── pom.xml └── README.md /res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DayS/uitableview4android/HEAD/res/drawable/icon.png -------------------------------------------------------------------------------- /res/drawable-hdpi/accessory_disclosure_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DayS/uitableview4android/HEAD/res/drawable-hdpi/accessory_disclosure_white.png -------------------------------------------------------------------------------- /res/drawable-ldpi/accessory_disclosure_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DayS/uitableview4android/HEAD/res/drawable-ldpi/accessory_disclosure_white.png -------------------------------------------------------------------------------- /res/drawable-mdpi/accessory_disclosure_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DayS/uitableview4android/HEAD/res/drawable-mdpi/accessory_disclosure_white.png -------------------------------------------------------------------------------- /res/drawable-hdpi/accessory_disclosure_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DayS/uitableview4android/HEAD/res/drawable-hdpi/accessory_disclosure_default.png -------------------------------------------------------------------------------- /res/drawable-ldpi/accessory_disclosure_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DayS/uitableview4android/HEAD/res/drawable-ldpi/accessory_disclosure_default.png -------------------------------------------------------------------------------- /res/drawable-mdpi/accessory_disclosure_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DayS/uitableview4android/HEAD/res/drawable-mdpi/accessory_disclosure_default.png -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/model/AccessoryType.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.model; 2 | 3 | public enum AccessoryType { 4 | NONE, DISCLOSURE 5 | // , DETAIL, CHECKMARK 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS junk files 2 | .DS_Store 3 | [Tt]humbs.db 4 | 5 | # Temp files 6 | *~ 7 | 8 | # Project files 9 | .class 10 | .classpath 11 | .project 12 | .settings/ 13 | target/ 14 | bin/ 15 | gen/ 16 | 17 | .springBeans 18 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Android-uitableview 4 | Hello world! 5 | Settings 6 | MainActivity 7 | 8 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/listener/OnCellClickListener.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.listener; 2 | 3 | import fr.days.android.uitableview.model.IndexPath; 4 | 5 | public interface OnCellClickListener { 6 | 7 | void onCellClick(IndexPath indexPath); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/listener/OnHeaderClickListener.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.listener; 2 | 3 | import fr.days.android.uitableview.model.IndexPath; 4 | 5 | public interface OnHeaderClickListener { 6 | 7 | void onHeaderClick(IndexPath indexPath); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/model/UITableItem.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.model; 2 | 3 | 4 | public abstract class UITableItem { 5 | 6 | public String title; 7 | 8 | public UITableItem(String title) { 9 | super(); 10 | this.title = title; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/listener/OnCellLongClickListener.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.listener; 2 | 3 | import fr.days.android.uitableview.model.IndexPath; 4 | 5 | public interface OnCellLongClickListener { 6 | 7 | boolean onCellLongClick(IndexPath indexPath); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/listener/OnHeaderLongClickListener.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.listener; 2 | 3 | import fr.days.android.uitableview.model.IndexPath; 4 | 5 | public interface OnHeaderLongClickListener { 6 | 7 | boolean onHeaderLongClick(IndexPath indexPath); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/listener/OnCellAccessoryClickListener.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.listener; 2 | 3 | import fr.days.android.uitableview.model.IndexPath; 4 | 5 | public interface OnCellAccessoryClickListener { 6 | 7 | void onCellAccessoryClick(IndexPath indexPath); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/model/UITableHeaderItem.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.model; 2 | 3 | public class UITableHeaderItem extends UITableItem { 4 | 5 | public UITableHeaderItem() { 6 | super(null); 7 | } 8 | 9 | public UITableHeaderItem(String title) { 10 | super(title); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /res/color/text_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /res/drawable/accessory_disclosure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/adapter/UITableViewInternalAccessoryListener.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.adapter; 2 | 3 | import fr.days.android.uitableview.model.IndexPath; 4 | 5 | /** 6 | * This listener is used to notify the {@link UITableViewAdapterInternal} that user clicked on an accessory and then notify the application 7 | * 8 | * @author dvilleneuve 9 | * 10 | */ 11 | public interface UITableViewInternalAccessoryListener { 12 | 13 | void onCellAccessoryClick(IndexPath indexPath); 14 | 15 | boolean onCellAccessoryLongClick(IndexPath indexPath); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-10 15 | android.library=true 16 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/model/UITableCellItem.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.model; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.drawable.Drawable; 5 | 6 | public class UITableCellItem extends UITableItem { 7 | 8 | public String subtitle; 9 | 10 | public int imageInt; 11 | public Drawable imageDrawable; 12 | public Bitmap imageBitmap; 13 | 14 | public AccessoryType accessory = AccessoryType.NONE; 15 | public Drawable accessoryDrawable; 16 | 17 | public UITableCellItem() { 18 | super(null); 19 | } 20 | 21 | public UITableCellItem(String title, String subtitle) { 22 | super(title); 23 | this.subtitle = subtitle; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/view/UITableItemView.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.view; 2 | 3 | import android.content.Context; 4 | import android.widget.LinearLayout; 5 | import fr.days.android.uitableview.model.IndexPath; 6 | 7 | public class UITableItemView extends LinearLayout { 8 | 9 | protected IndexPath indexPath; 10 | 11 | public UITableItemView(Context context, IndexPath indexPath) { 12 | super(context); 13 | this.indexPath = indexPath; 14 | } 15 | 16 | /** 17 | * Reset the {@link IndexPath} of this cell and so his background according to his position. 18 | * 19 | * @param indexPath 20 | */ 21 | public void setIndexPath(IndexPath indexPath) { 22 | this.indexPath = indexPath; 23 | } 24 | 25 | public IndexPath getIndexPath() { 26 | return indexPath; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #FFD3D3D3 6 | 7 | 8 | #FFFFFFFF 9 | #FFFFFFFF 10 | 11 | 12 | #FF3590C4 13 | #FF2570BA 14 | 15 | 16 | #000000 17 | #FFFFFF 18 | 19 | 20 | #4C566C 21 | -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /res/layout/table_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 24 | 25 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/adapter/UITableViewAdapter.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.adapter; 2 | 3 | import android.content.Context; 4 | import fr.days.android.uitableview.model.IndexPath; 5 | import fr.days.android.uitableview.model.UITableCellItem; 6 | import fr.days.android.uitableview.model.UITableHeaderItem; 7 | import fr.days.android.uitableview.view.UITableCellView; 8 | import fr.days.android.uitableview.view.UITableHeaderView; 9 | 10 | public abstract class UITableViewAdapter { 11 | 12 | public int numberOfGroups() { 13 | return 1; 14 | } 15 | 16 | public int numberOfRows(int group) { 17 | return 0; 18 | } 19 | 20 | public abstract UITableHeaderItem headerItemForGroup(Context context, IndexPath indexPath); 21 | 22 | public abstract UITableCellItem cellItemForRow(Context context, IndexPath indexPath); 23 | 24 | public abstract UITableHeaderView headerViewForGroup(Context context, IndexPath indexPath, UITableHeaderItem cellItem, UITableHeaderView convertView); 25 | 26 | public abstract UITableCellView cellViewForRow(Context context, IndexPath indexPath, UITableCellItem cellItem, UITableCellView convertView); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/view/UITableHeaderView.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.view; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.widget.TextView; 6 | import fr.days.android.uitableview.R; 7 | import fr.days.android.uitableview.model.IndexPath; 8 | 9 | public class UITableHeaderView extends UITableItemView { 10 | 11 | private TextView titleView; 12 | 13 | public UITableHeaderView(Context context, IndexPath indexPath) { 14 | super(context, indexPath); 15 | 16 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 17 | if (inflater != null) { 18 | inflater.inflate(R.layout.table_header, this); 19 | } 20 | 21 | titleView = (TextView) findViewById(R.id.title); 22 | } 23 | 24 | public UITableHeaderView(Context context, IndexPath indexPath, String title) { 25 | this(context, indexPath); 26 | setTitle(title); 27 | } 28 | 29 | public TextView getTitleView() { 30 | return titleView; 31 | } 32 | 33 | public String getTitle() { 34 | return titleView.getText().toString(); 35 | } 36 | 37 | public void setTitle(String title) { 38 | if (title == null) { 39 | titleView.setVisibility(GONE); 40 | } else { 41 | titleView.setVisibility(VISIBLE); 42 | } 43 | titleView.setText(title); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/drawable/UITableCellDrawable.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.drawable; 2 | 3 | import android.graphics.drawable.GradientDrawable; 4 | import android.graphics.drawable.GradientDrawable.Orientation; 5 | import android.graphics.drawable.StateListDrawable; 6 | 7 | /** 8 | * This class will ceate a the cell style at runtime. It's necessary to allow multiple background color in cells 9 | * 10 | * @author dvilleneuve 11 | * 12 | */ 13 | public class UITableCellDrawable extends StateListDrawable { 14 | 15 | public UITableCellDrawable(float topRadius, float bottomRadius, int[] colorDefault, int[] colorPressed, int borderColor) { 16 | addState(new int[] { -android.R.attr.state_pressed }, createGradientDrawable(topRadius, bottomRadius, colorDefault, borderColor)); 17 | addState(new int[] { android.R.attr.state_pressed }, createGradientDrawable(topRadius, bottomRadius, colorPressed, borderColor)); 18 | } 19 | 20 | private GradientDrawable createGradientDrawable(float topRadius, float bottomRadius, int[] color, int borderColor) { 21 | GradientDrawable gradientDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, color); 22 | gradientDrawable.setShape(GradientDrawable.RECTANGLE); 23 | gradientDrawable.setGradientRadius(270.0f); 24 | gradientDrawable.setStroke(1, borderColor); 25 | gradientDrawable.setCornerRadii(getRadii(topRadius, bottomRadius)); 26 | 27 | return gradientDrawable; 28 | } 29 | 30 | private float[] getRadii(float top, float bottom) { 31 | return new float[] { top, top, // 32 | top, top, // 33 | bottom, bottom, // 34 | bottom, bottom // 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/model/IndexPath.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.model; 2 | 3 | /** 4 | * This class represent an item by the group to which it belongs and his position in this group. 5 | */ 6 | public class IndexPath { 7 | private final int position; 8 | private final int group; 9 | private final int row; 10 | private final int groupsCount; 11 | private final int rowsCount; 12 | 13 | public IndexPath(int position, int group, int groupCount) { 14 | this(position, group, -1, groupCount, 0); 15 | } 16 | 17 | public IndexPath(int position, int group, int row, int groupsCount, int rowsCount) { 18 | this.position = position; 19 | this.group = group; 20 | this.row = row; 21 | this.groupsCount = groupsCount; 22 | this.rowsCount = rowsCount; 23 | } 24 | 25 | public int getPosition() { 26 | return position; 27 | } 28 | 29 | public int getGroup() { 30 | return group; 31 | } 32 | 33 | public int getRow() { 34 | return row; 35 | } 36 | 37 | public int getGroupsCount() { 38 | return groupsCount; 39 | } 40 | 41 | public int getRowsCount() { 42 | return rowsCount; 43 | } 44 | 45 | public boolean isHeader() { 46 | return row == -1; 47 | } 48 | 49 | public boolean isFirstGroup() { 50 | return group == 0; 51 | } 52 | 53 | public boolean isLastGroup() { 54 | return group == groupsCount - 1; 55 | } 56 | 57 | public boolean isFirstCellOfGroup() { 58 | return row == 0; 59 | } 60 | 61 | public boolean isLastCellOfGroup() { 62 | return row == rowsCount - 1; 63 | } 64 | 65 | public boolean isLastCell() { 66 | return isLastGroup() && isLastCellOfGroup(); 67 | } 68 | 69 | public boolean isStateEquals(IndexPath indexPath) { 70 | if (indexPath == null) 71 | return false; 72 | if (indexPath == this) 73 | return true; 74 | 75 | if (isFirstCellOfGroup() && !indexPath.isFirstCellOfGroup()) 76 | return false; 77 | else if (isLastCellOfGroup() && !indexPath.isLastCellOfGroup()) 78 | return false; 79 | else { 80 | if (indexPath.isFirstCellOfGroup() || indexPath.isLastCellOfGroup()) 81 | return false; 82 | } 83 | return true; 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return "IndexPath [position=" + position + ", group=" + group + "/" + groupsCount + ", row=" + row + "/" + rowsCount + "]"; 89 | } 90 | } -------------------------------------------------------------------------------- /res/layout/table_cell.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 27 | 28 | 37 | 38 | 48 | 49 | 50 | 59 | 60 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | fr.days.android 5 | uitableview4android 6 | 1.1-SNAPSHOT 7 | apklib 8 | 9 | 10 | scm:git|ssh://git@github.com/DayS/uitableview4android.git 11 | HEAD 12 | 13 | 14 | 15 | UTF-8 16 | 1.6 17 | 18 | 2.3.2 19 | 2.3.2 20 | 3.3.0 21 | 22 | 2.3.3 23 | 10 24 | 25 | 26 | 27 | 28 | 29 | com.google.android 30 | android 31 | ${android.sdk.version} 32 | provided 33 | 34 | 35 | 36 | 37 | src 38 | 39 | 40 | maven-compiler-plugin 41 | ${maven.compiler.plugin.version} 42 | 43 | ${java.version} 44 | ${java.version} 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-release-plugin 50 | ${maven-release-plugin.version} 51 | 52 | v@{project.version} 53 | 54 | 55 | 56 | com.jayway.maven.plugins.android.generation2 57 | android-maven-plugin 58 | ${android.maven.plugin.version} 59 | true 60 | 61 | true 62 | 63 | ${env.ANDROID_HOME} 64 | ${android.sdk.level} 65 | 66 | false 67 | 68 | 69 | -Xmx512m 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/view/UITableView.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.widget.AdapterView; 9 | import android.widget.ListView; 10 | import fr.days.android.uitableview.adapter.UITableViewAdapter; 11 | import fr.days.android.uitableview.adapter.UITableViewAdapterInternal; 12 | import fr.days.android.uitableview.adapter.UITableViewInternalAccessoryListener; 13 | import fr.days.android.uitableview.listener.OnCellAccessoryClickListener; 14 | import fr.days.android.uitableview.listener.OnCellClickListener; 15 | import fr.days.android.uitableview.listener.OnCellLongClickListener; 16 | import fr.days.android.uitableview.listener.OnHeaderClickListener; 17 | import fr.days.android.uitableview.listener.OnHeaderLongClickListener; 18 | import fr.days.android.uitableview.model.IndexPath; 19 | 20 | public class UITableView extends ListView implements android.widget.AdapterView.OnItemClickListener, android.widget.AdapterView.OnItemLongClickListener, UITableViewInternalAccessoryListener { 21 | 22 | private UITableViewAdapterInternal tableViewAdapterInternal; 23 | private OnCellClickListener onCellClickListener; 24 | private OnCellLongClickListener onCellLongClickListener; 25 | private OnCellAccessoryClickListener onCellAccessoryClickListener; 26 | private OnHeaderClickListener onHeaderClickListener; 27 | private OnHeaderLongClickListener onHeaderLongClickListener; 28 | 29 | public UITableView(Context context) { 30 | super(context); 31 | init(); 32 | } 33 | 34 | public UITableView(Context context, AttributeSet attrs, int defStyle) { 35 | super(context, attrs, defStyle); 36 | init(); 37 | } 38 | 39 | public UITableView(Context context, AttributeSet attrs) { 40 | super(context, attrs); 41 | init(); 42 | } 43 | 44 | private void init() { 45 | setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 46 | setBackgroundColor(Color.parseColor("#f1f6f9")); 47 | setDivider(new ColorDrawable(Color.TRANSPARENT)); 48 | setSelector(new ColorDrawable(Color.TRANSPARENT)); 49 | 50 | // Init internal listeners 51 | setOnItemClickListener(this); 52 | setOnItemLongClickListener(this); 53 | } 54 | 55 | public void setAdapter(UITableViewAdapter tableViewAdapter) { 56 | tableViewAdapterInternal = new UITableViewAdapterInternal(getContext(), tableViewAdapter); 57 | tableViewAdapterInternal.setInternalAccessoryListener(this); 58 | super.setAdapter(tableViewAdapterInternal); 59 | } 60 | 61 | public void setOnCellClickListener(OnCellClickListener tableCellListener) { 62 | this.onCellClickListener = tableCellListener; 63 | } 64 | 65 | public void setOnCellLongClickListener(OnCellLongClickListener onCellLongClickListener) { 66 | this.onCellLongClickListener = onCellLongClickListener; 67 | } 68 | 69 | public void setOnCellAccessoryClickListener(OnCellAccessoryClickListener onCellAccessoryClickListener) { 70 | this.onCellAccessoryClickListener = onCellAccessoryClickListener; 71 | } 72 | 73 | public void setOnHeaderClickListener(OnHeaderClickListener onHeaderClickListener) { 74 | this.onHeaderClickListener = onHeaderClickListener; 75 | } 76 | 77 | public void setOnHeaderLongClickListener(OnHeaderLongClickListener onHeaderLongClickListener) { 78 | this.onHeaderLongClickListener = onHeaderLongClickListener; 79 | } 80 | 81 | @Override 82 | public void onItemClick(AdapterView parent, View view, int position, long id) { 83 | IndexPath indexPath = tableViewAdapterInternal.retrieveIndexPathByPosition(position); 84 | if (indexPath != null) { 85 | if (indexPath.isHeader()) { 86 | if (onHeaderClickListener != null) { 87 | onHeaderClickListener.onHeaderClick(indexPath); 88 | } 89 | } else { 90 | if (onCellClickListener != null) { 91 | onCellClickListener.onCellClick(indexPath); 92 | } 93 | } 94 | } 95 | } 96 | 97 | @Override 98 | public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { 99 | IndexPath indexPath = tableViewAdapterInternal.retrieveIndexPathByPosition(position); 100 | if (indexPath != null) { 101 | if (indexPath.isHeader()) { 102 | if (onHeaderLongClickListener != null) { 103 | return onHeaderLongClickListener.onHeaderLongClick(indexPath); 104 | } 105 | } else { 106 | if (onCellLongClickListener != null) { 107 | return onCellLongClickListener.onCellLongClick(indexPath); 108 | } 109 | } 110 | } 111 | return false; 112 | } 113 | 114 | @Override 115 | public void onCellAccessoryClick(IndexPath indexPath) { 116 | if (indexPath != null && !indexPath.isHeader()) { 117 | // Trigger the accessory listener if set, else tigger the cell listener 118 | if (onCellAccessoryClickListener != null) { 119 | onCellAccessoryClickListener.onCellAccessoryClick(indexPath); 120 | } else if (onCellClickListener != null) { 121 | onCellClickListener.onCellClick(indexPath); 122 | } 123 | } 124 | } 125 | 126 | @Override 127 | public boolean onCellAccessoryLongClick(IndexPath indexPath) { 128 | if (indexPath != null && !indexPath.isHeader()) { 129 | if (onCellLongClickListener != null) { 130 | return onCellLongClickListener.onCellLongClick(indexPath); 131 | } 132 | } 133 | return false; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is UITableView4Android ? 2 | 3 | UITableView4Android is an Android library providing a customized ListView with the look & field and behavior of UITableView on iOS. 4 | 5 | **Note: This is not a recommended Android UI pattern! This repo is only here in case your client insist to do this.** 6 | 7 | # Motivation 8 | 9 | Some companies are asking for mobile applications that must have the same appearance both on Android and iOS. And of course, iOS is almost always the version to fit... 10 | List views are one the most difficult component to adapt on Android because of corners and borders to set correctly. 11 | 12 | # Usage example 13 | 14 | ## 1. Declare layout 15 | 16 | Add the UITableView in your layout : 17 | ```xml 18 | 24 | 25 | ``` 26 | 27 | ## 2. Implement the adapter 28 | 29 | This `UITableView` is a sub-class of Android ListView and need an implementation of `UITableViewAdapter`. 30 | 31 | ```java 32 | class SimpleUITableViewAdapter extends UITableViewAdapter { 33 | 34 | private int[] color_line1_default; 35 | private int[] color_line2_default; 36 | private int[] color_line1_pressed; 37 | private int[] color_line2_pressed; 38 | 39 | public SimpleUITableViewAdapter() { 40 | // Prepare two sets of colors for odd and even lines 41 | color_line1_default = new int[] { getResources().getColor(R.color.base_start_color_line1_default), getResources().getColor(R.color.base_end_color_line1_default) }; 42 | color_line2_default = new int[] { getResources().getColor(R.color.base_start_color_line2_default), getResources().getColor(R.color.base_end_color_line2_default) }; 43 | color_line1_pressed = new int[] { getResources().getColor(R.color.base_start_color_line1_pressed), getResources().getColor(R.color.base_end_color_line1_pressed) }; 44 | color_line2_pressed = new int[] { getResources().getColor(R.color.base_start_color_line2_pressed), getResources().getColor(R.color.base_end_color_line2_pressed) }; 45 | } 46 | 47 | @Override 48 | public int numberOfGroups() { 49 | return 4; 50 | } 51 | 52 | @Override 53 | public int numberOfRows(int group) { 54 | return (group + 1) * 2; 55 | } 56 | 57 | @Override 58 | public UITableHeaderItem headerItemForGroup(Context context, IndexPath indexPath) { 59 | return new UITableHeaderItem("Group " + indexPath.getGroup()); 60 | } 61 | 62 | @Override 63 | public UITableCellItem cellItemForRow(Context context, IndexPath indexPath) { 64 | String title = "Cell number " + indexPath.getRow() + " in group " + indexPath.getGroup(); 65 | String subtitle = (indexPath.getRow() % 2 == 0) ? "Subtitle " + indexPath.getRow() : null; 66 | return new UITableCellItem(title, subtitle); 67 | } 68 | 69 | @Override 70 | public UITableHeaderView headerViewForGroup(Context context, IndexPath indexPath, UITableHeaderItem headerItem, UITableHeaderView convertView) { 71 | UITableHeaderView headerView; 72 | if (convertView == null) { 73 | // If the recycled view is null, we just creating one 74 | headerView = new UITableHeaderView(context, indexPath); 75 | } else { 76 | headerView = (UITableHeaderView) convertView; 77 | } 78 | 79 | headerView.setTitle(headerItem.title); 80 | 81 | return headerView; 82 | } 83 | 84 | @Override 85 | public UITableCellView cellViewForRow(Context context, IndexPath indexPath, UITableCellItem cellItem, UITableCellView convertView) { 86 | UITableCellView cellView; 87 | if (convertView == null) { 88 | // If the recycled view is null, we just creating one with cell's commons parameters 89 | cellView = new UITableCellView(context, indexPath); 90 | cellView.setMinimumHeight(80); 91 | cellView.setAccessory(AccessoryType.DISCLOSURE); 92 | } else { 93 | cellView = (UITableCellView) convertView; 94 | } 95 | 96 | cellView.setTitle(cellItem.title); 97 | cellView.setSubtitle(cellItem.subtitle); 98 | 99 | // Set alternated background color 100 | if (indexPath.getRow() % 2 == 0) { 101 | cellView.setBackgroundColor(color_line1_default, color_line1_pressed); 102 | } else { 103 | cellView.setBackgroundColor(color_line2_default, color_line2_pressed); 104 | } 105 | 106 | return cellView; 107 | } 108 | } 109 | ``` 110 | 111 | ## 3. Add listeners 112 | 113 | Some listeners are avaible to handle click and long click on cells, headers and accessory's cells. Let's implement them all on our `SimpleUITableViewAdapter`. 114 | 115 | ```java 116 | @Override 117 | public void onCellClick(IndexPath indexPath) { 118 | Toast.makeText(getApplicationContext(), "Cell clicked : " + indexPath, 1000).show(); 119 | } 120 | 121 | @Override 122 | public boolean onCellLongClick(IndexPath indexPath) { 123 | Toast.makeText(getApplication(), "Cell long clicked : " + indexPath, 1000).show(); 124 | return indexPath.getRow() % 2 == 0; // Consume the long click one row out of two 125 | } 126 | 127 | @Override 128 | public void onCellAccessoryClick(IndexPath indexPath) { 129 | Toast.makeText(getApplication(), "Cell accessory clicked : " + indexPath, 1000).show(); 130 | } 131 | 132 | @Override 133 | public void onHeaderClick(IndexPath indexPath) { 134 | Toast.makeText(getApplicationContext(), "Header clicked : " + indexPath, 1000).show(); 135 | } 136 | 137 | @Override 138 | public boolean onHeaderLongClick(IndexPath indexPath) { 139 | Toast.makeText(getApplicationContext(), "Header long clicked : " + indexPath, 1000).show(); 140 | return indexPath.getGroup() % 2 == 0; // Consume the long click one row out of two 141 | } 142 | ``` 143 | 144 | ## 4. Configure UITableView 145 | 146 | Now, we have an implementation of `UITableViewAdapter` with all listeners added. It only remains to configure the `UITableView`. 147 | 148 | ```java 149 | SimpleUITableViewAdapter tableViewAdapter = new SimpleUITableViewAdapter(); 150 | tableView = (UITableView) findViewById(R.id.listView); 151 | tableView.setAdapter(tableViewAdapter); 152 | tableView.setOnCellClickListener(tableViewAdapter); 153 | tableView.setOnCellLongClickListener(tableViewAdapter); 154 | tableView.setOnCellAccessoryClickListener(tableViewAdapter); 155 | tableView.setOnHeaderClickListener(tableViewAdapter); 156 | tableView.setOnHeaderLongClickListener(tableViewAdapter); 157 | ``` 158 | 159 | # Result 160 | 161 | And here is the result. 162 | 163 | ![Example result](https://github.com/DayS/uitableview4android/wiki/img/example_result.png) 164 | -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/adapter/UITableViewAdapterInternal.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.adapter; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import android.content.Context; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.BaseAdapter; 10 | import fr.days.android.uitableview.model.IndexPath; 11 | import fr.days.android.uitableview.model.UITableCellItem; 12 | import fr.days.android.uitableview.model.UITableHeaderItem; 13 | import fr.days.android.uitableview.view.UITableCellView; 14 | import fr.days.android.uitableview.view.UITableHeaderView; 15 | 16 | /** 17 | * Internal adapter used by Android ListView. 18 | * 19 | * @author dvilleneuve 20 | * 21 | */ 22 | public class UITableViewAdapterInternal extends BaseAdapter { 23 | 24 | private static final int VIEW_TYPE_HEADER = 0; 25 | private static final int VIEW_TYPE_CELL = 1; 26 | 27 | private final Context context; 28 | private final UITableViewAdapter tableViewAdapter; 29 | 30 | private UITableViewInternalAccessoryListener internalAccessoryListener; 31 | private Map indexPaths = new HashMap(); 32 | 33 | /** 34 | * @param uiTableViewAdapter 35 | */ 36 | public UITableViewAdapterInternal(Context context, UITableViewAdapter uiTableViewAdapter) { 37 | this.context = context; 38 | this.tableViewAdapter = uiTableViewAdapter; 39 | } 40 | 41 | @Override 42 | public int getViewTypeCount() { 43 | return 2; 44 | } 45 | 46 | @Override 47 | public int getItemViewType(int position) { 48 | IndexPath indexPath = retrieveIndexPathByPosition(position); 49 | if (indexPath == null) 50 | return IGNORE_ITEM_VIEW_TYPE; 51 | else if (indexPath.isHeader()) 52 | return VIEW_TYPE_HEADER; 53 | else 54 | return VIEW_TYPE_CELL; 55 | } 56 | 57 | @Override 58 | public int getCount() { 59 | int numberOfGroups = tableViewAdapter.numberOfGroups(); 60 | int countItems = numberOfGroups; 61 | for (int group = 0; group < numberOfGroups; group++) { 62 | countItems += tableViewAdapter.numberOfRows(group); 63 | } 64 | return countItems; 65 | } 66 | 67 | @Override 68 | public Object getItem(int position) { 69 | IndexPath indexPath = retrieveIndexPathByPosition(position); 70 | if (indexPath == null) { 71 | throw new NullPointerException("Unable to retrieve index path for position " + position); 72 | } else if (indexPath.isHeader()) { 73 | return getHeaderItem(indexPath); 74 | } else { 75 | return tableViewAdapter.cellItemForRow(context, indexPath); 76 | } 77 | } 78 | 79 | public UITableHeaderItem getHeaderItem(IndexPath indexPath) { 80 | UITableHeaderItem headerItem = tableViewAdapter.headerItemForGroup(context, indexPath); 81 | return headerItem != null ? headerItem : new UITableHeaderItem(); 82 | } 83 | 84 | @Override 85 | public long getItemId(int position) { 86 | return position; 87 | } 88 | 89 | @Override 90 | public View getView(int position, View convertView, ViewGroup parent) { 91 | IndexPath indexPath = retrieveIndexPathByPosition(position); 92 | 93 | if (indexPath == null) { 94 | throw new NullPointerException("Unable to retrieve index path for position " + position); 95 | } else if (indexPath.isHeader()) { 96 | if (!(convertView instanceof UITableHeaderView)) { 97 | convertView = null; 98 | } 99 | if (convertView != null) { 100 | UITableHeaderView tableHeaderView = (UITableHeaderView) convertView; 101 | tableHeaderView.setIndexPath(indexPath); 102 | } 103 | UITableHeaderItem headerItem = getHeaderItem(indexPath); 104 | UITableHeaderView headerView = tableViewAdapter.headerViewForGroup(context, indexPath, headerItem, (UITableHeaderView) convertView); 105 | return headerView; 106 | } else { 107 | if (!(convertView instanceof UITableCellView)) { 108 | convertView = null; 109 | } 110 | if (convertView != null) { 111 | // Configure the recycling view by reseting his indexpath and background 112 | UITableCellView tableCellView = (UITableCellView) convertView; 113 | tableCellView.setIndexPath(indexPath); 114 | if (!tableCellView.getIndexPath().isStateEquals(indexPath)) { 115 | tableCellView.setDefaultBackgroundColor(); 116 | } 117 | } 118 | UITableCellItem cellItem = tableViewAdapter.cellItemForRow(context, indexPath); 119 | UITableCellView cellView = tableViewAdapter.cellViewForRow(context, indexPath, cellItem, (UITableCellView) convertView); 120 | cellView.setInternalAccessoryListener(internalAccessoryListener); 121 | return cellView; 122 | } 123 | } 124 | 125 | /** 126 | * Retrieve an {@link IndexPath} according to a ListView's item position. 127 | * 128 | * @param position 129 | * @return An {@link IndexPath} if position is valid, null else 130 | */ 131 | public IndexPath retrieveIndexPathByPosition(final int position) { 132 | IndexPath indexPath = indexPaths.get(position); 133 | if (indexPath == null) { 134 | int numberOfGroups = tableViewAdapter.numberOfGroups(); 135 | 136 | if (position == 0) { // Shortcut for the first item 137 | indexPath = new IndexPath(0, 0, numberOfGroups); 138 | 139 | } else { 140 | int numberOfRowsBefore = 0; 141 | for (int group = 0; group < numberOfGroups; group++) { 142 | int numberOfRows = tableViewAdapter.numberOfRows(group); 143 | 144 | // Header 145 | if (position == numberOfRowsBefore) { 146 | indexPath = new IndexPath(position, group, numberOfGroups); 147 | break; 148 | } 149 | // Cell 150 | if (position <= numberOfRowsBefore + numberOfRows) { 151 | indexPath = new IndexPath(position, group, position - numberOfRowsBefore - 1, numberOfGroups, numberOfRows); 152 | break; 153 | } 154 | 155 | // This position doesn't fit to this group, see the next one 156 | numberOfRowsBefore += numberOfRows + 1; // rows + header 157 | } 158 | } 159 | 160 | indexPaths.put(position, indexPath); 161 | } 162 | return indexPath; 163 | } 164 | 165 | @Override 166 | public void notifyDataSetChanged() { 167 | indexPaths.clear(); 168 | super.notifyDataSetChanged(); 169 | } 170 | 171 | @Override 172 | public void notifyDataSetInvalidated() { 173 | indexPaths.clear(); 174 | super.notifyDataSetInvalidated(); 175 | } 176 | 177 | public UITableViewInternalAccessoryListener getInternalAccessoryListener() { 178 | return internalAccessoryListener; 179 | } 180 | 181 | public void setInternalAccessoryListener(UITableViewInternalAccessoryListener internalAccessoryListener) { 182 | this.internalAccessoryListener = internalAccessoryListener; 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /src/fr/days/android/uitableview/view/UITableCellView.java: -------------------------------------------------------------------------------- 1 | package fr.days.android.uitableview.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Rect; 6 | import android.graphics.drawable.Drawable; 7 | import android.graphics.drawable.InsetDrawable; 8 | import android.text.TextUtils; 9 | import android.view.LayoutInflater; 10 | import android.view.TouchDelegate; 11 | import android.view.View; 12 | import android.widget.ImageView; 13 | import android.widget.TextView; 14 | import fr.days.android.uitableview.R; 15 | import fr.days.android.uitableview.adapter.UITableViewInternalAccessoryListener; 16 | import fr.days.android.uitableview.drawable.UITableCellDrawable; 17 | import fr.days.android.uitableview.model.AccessoryType; 18 | import fr.days.android.uitableview.model.IndexPath; 19 | 20 | public class UITableCellView extends UITableItemView { 21 | 22 | private static final int INSET = 10; 23 | 24 | private static int[] colorLineDefault; 25 | private static int[] colorLinePressed; 26 | private static int borderColor = Integer.MIN_VALUE; 27 | 28 | private ImageView imageView; 29 | private TextView titleView; 30 | private TextView subtitleView; 31 | private ImageView accessoryView; 32 | private UITableViewInternalAccessoryListener internalAccessoryListener; 33 | 34 | public UITableCellView(Context context, IndexPath indexPath) { 35 | super(context, indexPath); 36 | 37 | if (borderColor == Integer.MIN_VALUE) { 38 | borderColor = getColor(R.color.cell_border); 39 | colorLineDefault = new int[] { getResources().getColor(R.color.base_start_color_line_default), getResources().getColor(R.color.base_end_color_line_default) }; 40 | colorLinePressed = new int[] { getResources().getColor(R.color.base_start_color_line_pressed), getResources().getColor(R.color.base_end_color_line_pressed) }; 41 | } 42 | 43 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 44 | if (inflater != null) { 45 | inflater.inflate(R.layout.table_cell, this); 46 | } 47 | 48 | imageView = (ImageView) findViewById(R.id.image); 49 | titleView = (TextView) findViewById(R.id.title); 50 | subtitleView = (TextView) findViewById(R.id.subtitle); 51 | accessoryView = (ImageView) findViewById(R.id.accessory); 52 | 53 | // Set default color 54 | setDefaultBackgroundColor(); 55 | 56 | // Increase the touchable area for accessoryView 57 | post(getTouchDelegateAction(this, accessoryView, 30, 30, 30, 30)); 58 | } 59 | 60 | public UITableCellView(Context context, IndexPath indexPath, String title, String subtitle) { 61 | this(context, indexPath); 62 | setTitle(title); 63 | setSubtitle(subtitle); 64 | } 65 | 66 | private int getColor(int colorId) { 67 | return getResources().getColor(colorId); 68 | } 69 | 70 | public ImageView getImageView() { 71 | return imageView; 72 | } 73 | 74 | public void setImage(Integer imageResource) { 75 | if (imageResource == null) { 76 | imageView.setVisibility(View.GONE); 77 | } else { 78 | imageView.setVisibility(View.VISIBLE); 79 | imageView.setImageResource(imageResource); 80 | } 81 | } 82 | 83 | public void setImage(Drawable drawable) { 84 | if (drawable == null) { 85 | imageView.setVisibility(View.GONE); 86 | } else { 87 | imageView.setVisibility(View.VISIBLE); 88 | imageView.setImageDrawable(drawable); 89 | } 90 | } 91 | 92 | public void setImageBitmap(Bitmap bitmap) { 93 | if (bitmap == null) { 94 | imageView.setVisibility(View.GONE); 95 | } else { 96 | imageView.setVisibility(View.VISIBLE); 97 | imageView.setImageBitmap(bitmap); 98 | } 99 | } 100 | 101 | public TextView getTitleView() { 102 | return titleView; 103 | } 104 | 105 | public String getTitle() { 106 | return titleView.getText().toString(); 107 | } 108 | 109 | public void setTitle(String title) { 110 | this.titleView.setText(title); 111 | } 112 | 113 | public TextView getSubtitleView() { 114 | return subtitleView; 115 | } 116 | 117 | public String getSubtitle() { 118 | return subtitleView.getText().toString(); 119 | } 120 | 121 | public void setSubtitle(String subtitle) { 122 | if (TextUtils.isEmpty(subtitle)) { 123 | subtitleView.setVisibility(View.GONE); 124 | } else { 125 | subtitleView.setVisibility(View.VISIBLE); 126 | subtitleView.setText(subtitle); 127 | } 128 | } 129 | 130 | public ImageView getAccessoryView() { 131 | return accessoryView; 132 | } 133 | 134 | public void setAccessory(AccessoryType accessoryType) { 135 | if (accessoryType == AccessoryType.NONE) { 136 | accessoryView.setVisibility(View.GONE); 137 | } else { 138 | accessoryView.setVisibility(View.VISIBLE); 139 | 140 | switch (accessoryType) { 141 | case DISCLOSURE: 142 | accessoryView.setImageResource(R.drawable.accessory_disclosure); 143 | break; 144 | } 145 | } 146 | } 147 | 148 | public void setAccessory(Drawable drawable) { 149 | if (drawable == null) { 150 | accessoryView.setVisibility(View.GONE); 151 | } else { 152 | accessoryView.setVisibility(View.VISIBLE); 153 | accessoryView.setImageDrawable(drawable); 154 | } 155 | } 156 | 157 | public void setDefaultBackgroundColor() { 158 | setBackgroundColor(colorLineDefault, colorLinePressed); 159 | } 160 | 161 | public void setBackgroundColor(int[] colorDefault, int[] colorPressed) { 162 | // Assign the right backgroundDrawable according to the cell's position in the group 163 | Drawable backgroundDrawable; 164 | if (indexPath.getRowsCount() == 1) { 165 | backgroundDrawable = new UITableCellDrawable(10.0f, 10.0f, colorDefault, colorPressed, borderColor); 166 | } else { 167 | if (indexPath.isFirstCellOfGroup()) { 168 | backgroundDrawable = new UITableCellDrawable(10.0f, 0, colorDefault, colorPressed, borderColor); 169 | } else if (indexPath.isLastCellOfGroup()) { 170 | backgroundDrawable = new UITableCellDrawable(0, 10.0f, colorDefault, colorPressed, borderColor); 171 | } else { 172 | backgroundDrawable = new UITableCellDrawable(0, 0, colorDefault, colorPressed, borderColor); 173 | } 174 | } 175 | 176 | // Add extra space if this cell is the last one 177 | int bottomInset = 0; 178 | if (indexPath.isLastCell()) { 179 | bottomInset = INSET; 180 | } 181 | setBackgroundDrawable(new InsetDrawable(backgroundDrawable, INSET, 0, INSET, bottomInset)); 182 | } 183 | 184 | public UITableViewInternalAccessoryListener getInternalAccessoryListener() { 185 | return internalAccessoryListener; 186 | } 187 | 188 | public void setInternalAccessoryListener(final UITableViewInternalAccessoryListener internalAccessoryListener) { 189 | if (internalAccessoryListener != null) { 190 | accessoryView.setOnClickListener(new OnClickListener() { 191 | @Override 192 | public void onClick(View v) { 193 | internalAccessoryListener.onCellAccessoryClick(UITableCellView.this.indexPath); 194 | } 195 | }); 196 | accessoryView.setOnLongClickListener(new OnLongClickListener() { 197 | @Override 198 | public boolean onLongClick(View v) { 199 | return internalAccessoryListener.onCellAccessoryLongClick(UITableCellView.this.indexPath); 200 | } 201 | }); 202 | } 203 | 204 | this.internalAccessoryListener = internalAccessoryListener; 205 | } 206 | 207 | @Override 208 | public void setIndexPath(IndexPath indexPath) { 209 | super.setIndexPath(indexPath); 210 | setBackgroundColor(colorLineDefault, colorLinePressed); 211 | } 212 | 213 | /** 214 | * Adds a touchable padding around a View by constructing a TouchDelegate and adding it to parent View. 215 | * 216 | * @param parent 217 | * The "outer" parent View 218 | * @param delegate 219 | * The delegate that handles the TouchEvents 220 | * @param topPadding 221 | * Additional touch area in pixels above View 222 | * @param bootomPadding 223 | * Additional touch area in pixels below View 224 | * @param topPadding 225 | * Additional touch area in pixels left to View 226 | * @param topPadding 227 | * Additional touch area in pixels right to View 228 | * @return A runnable that you can post as action to a Views event queue 229 | */ 230 | private static Runnable getTouchDelegateAction(final View parent, final View delegate, final int topPadding, final int bottomPadding, final int leftPadding, final int rightPadding) { 231 | return new Runnable() { 232 | @Override 233 | public void run() { 234 | 235 | // Construct a new Rectangle and let the Delegate set its values 236 | Rect touchRect = new Rect(); 237 | delegate.getHitRect(touchRect); 238 | 239 | // Modify the dimensions of the Rectangle 240 | // Padding values below zero are replaced by zeros 241 | touchRect.top -= Math.max(0, topPadding); 242 | touchRect.bottom += Math.max(0, bottomPadding); 243 | touchRect.left -= Math.max(0, leftPadding); 244 | touchRect.right += Math.max(0, rightPadding); 245 | 246 | // Now we are going to construct the TouchDelegate 247 | TouchDelegate touchDelegate = new TouchDelegate(touchRect, delegate); 248 | 249 | // And set it on the parent 250 | parent.setTouchDelegate(touchDelegate); 251 | 252 | } 253 | }; 254 | } 255 | 256 | } 257 | --------------------------------------------------------------------------------