SwipeListView in ExpandableListView, is it possible? - android

I am using https://github.com/47deg/android-swipelistview to create a list view with swipeable items. I am wondering is it possible to apply this to ExpandableListView so that child elements can be swiped.

I've been able to make it work (at least for me). Here's a list of the steps that I took:
Copy SwipeListView class and rename to ExpandableSwipeListView.
Make it extend from ExpandableListView.
Copy SwipeListViewTouchListener class and rename to ExpandableSwipeListViewTouchListener
Change SwipeListViewTouchListener calls inside ExpandableSwipeListView to ExpandableSwipeListViewTouchListener.
Change SwipeListView calls inside SwipeListViewTouchListener to ExpandableSwipeListView.
Change method resetItems of ExpandableSwipeListViewTouchListener to this:
-
/**
* Adds new items when adapter is modified
*/
public void resetItems() {
ExpandableListAdapter adp=swipeListView.getExpandableListAdapter();
if (adp != null) {
int count = 0;
for (int i=0; i<adp.getGroupCount();i++){
//Add the total children and the group itself.
count+=adp.getChildrenCount(i) + 1;
}
for (int i = opened.size(); i <= count; i++) {
opened.add(false);
openedRight.add(false);
checked.add(false);
}
}
}
Create expandableswipelistview__attrs.xml on res/values with teh following:
<declare-styleable name="ExpandableSwipeListView">
<attr name="swipeOpenOnLongPress"/>
<attr name="swipeAnimationTime"/>
<attr name="swipeOffsetLeft"/>
<attr name="swipeOffsetRight"/>
<attr name="swipeCloseAllItemsWhenMoveList"/>
<attr name="swipeFrontView"/>
<attr name="swipeBackView"/>
<attr name="swipeGroupView" format="reference"/>
<attr name="swipeMode"/>
<attr name="swipeActionLeft"/>
<attr name="swipeActionRight"/>
<attr name="swipeDrawableChecked"/>
<attr name="swipeDrawableUnchecked"/>
</declare-styleable>
Add tag swipe:swipeGroupView to the declaration of your ExpandableSwipeListView on the layout. Example:
-
swipe:swipeGroupView="#+id/group"
The id should be something unique and that must be declared on the layout of your groups.
On init method of ExpandableSwipeListView change all styleables to "ExpandableSwipeListView_..." and on "obtainStyledAttributes" set it to R.styleable.ExpandableSwipeListView.
Add swipeGroupView like swipeFrontView on the init method and pass it to ExpandableSwipeListViewTouchListener constructor.
On ExpandableSwipeListViewTouchListener add the following code after "if (allowSwipe && rect.contains(x, y)) {":
-
//verify if it is a group:
if (child.findViewById(swipeGroupView)!=null){
return false;
}
Add these methods to ExpandableSwipeListView. They are helpful to make dismiss callbacks and other things:
-
/**
* Returns the group and child positions for a child element.
* This values are passed inside an array of dimension 2 where the index 0 is the group position and the index 1 is the child position.
* #param general_position used on the list (compatible with getChildAt)
* #return int[2] 0 => group position; 1 => child position
*/
public int[] getGroupAndChildPositions(int general_position){
//get group and child ids
int groupPosition=0;
int childPosition=0;
int helper=general_position;
for (int i=0;i<getExpandableListAdapter().getGroupCount();i++){
if (helper-getExpandableListAdapter().getChildrenCount(i)-1<=0){
groupPosition=i;
childPosition=helper-1;
break;
} else {
helper-=getExpandableListAdapter().getChildrenCount(i)+1;
}
}
return new int[]{groupPosition,childPosition};
}
/**
* Returns the general position of an element on the list (used by getChildAt)
* #param groupPosition
* #param childPosition
* #return the position on the list
*/
public int getGeneralPosition(int groupPosition, int childPosition){
int position=0;
for (int i=0;i<=groupPosition;i++){
if (i<groupPosition)
position+=getExpandableListAdapter().getChildrenCount(i)+1;
else{
position+=childPosition+1;
}
}
return position;
}

Related

android nested custom control xml attributes

I'm building an android compound control and nesting it into another compound control. The nested control, ThirtySecondIncrement, is a simple incrementing control with a minus then text field then plus so you can raise or lower the increment. I've made this control more general for my app allowing for a simple counter or 30-second increments or 1-minute increments. Here is the xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="counter_simple">0</integer>
<integer name="counter_30sec">1</integer>
<integer name="counter_1min">2</integer>
<declare-styleable name="ThirtySecondIncrement">
<attr name="countertype" format="integer"/>
<attr name="maxcount" format="integer"/>
</declare-styleable>
<declare-styleable name="IntervalEdit">
<attr name="label" format="string"/>
<attr name="incrementlabel" format="string"/>
</declare-styleable>
</resources>
My outer control includes labels and the ThirtySecondIncrement control. I would like to make the outer control flexible enough that I could include the "countertype" style to the outer control.
Can I do this in xml or must I do it programmatically? And if I do it programmatically how can I guarantee that it is done before the control is first used. Here is the code to extract the xml attributes:
public class ThirtySecondIncrement extends LinearLayout {
final int COUNT_INTEGER = 0;
final int COUNT_30SEC = 1;
final int COUNT_1MIN = 2;
//other code
public ThirtySecondIncrement(Context context, AttributeSet attr) {
super(context, attr);
TypedArray array = context.obtainStyledAttributes(attr, R.styleable.ThirtySecondIncrement, 0, 0);
m_countertype = array.getInt(R.styleable.ThirtySecondIncrement_countertype, COUNT_30SEC);
m_max = array.getInt(R.styleable.ThirtySecondIncrement_maxcount, MAXCOUNT);
m_increment = (m_countertype == COUNT_1MIN) ? 2 : 1;
array.recycle();
Initialize(context);
}
In a similar function in my IntervalEdit I could get an attribute relating to the counter and use a public function in ThirtySecondIncrement to set the countertype but, as stated, I'm wondering if there's a way to do this in xml.
thanks, in advance
I waited a while but got no answer so I solved the problem by having a helper function in the nested control to enable to set the property at runtime.
public void SetCounterType(integer countertype) {
//after checking that countertype is a valid value
m_countertype = countertype;
}
then in the constructor for IntervalEdit:
public IntervalEdit(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.IntervalEdit, 0, 0);
//other attributes read here
m_counterstyle = array.getInt(R.styleable.IntervalEdit_counterstyle, R.integer.counter_simple);
(ThirtySecondIncrement)findViewById(R.id.tsi).SetCounterStyle(m_counterstyle);
The SetCounterStyle function in the first custom control only sets the variable and doesn't force a redraw enabling me to call it from within the including custom control's constructor.
Any, that's how I solved it.

SeekBarPreference seekBarIncrement

I want to set seekBarIncrement in an xml (rather than programmatically). I have tried many variants of adding it to my xml, including in a style, as seekBarIncrement="100", asp:seekBarIncrement="100" etc. Nothing breaks/complains, but there is no increment either --- the seekbar values all differ only by 1, not 100, and if I put a log in code, it hasn't seen any increase there either.
How do I get seekBarIncrement to take effect? I'm using android.support.v7.preference.SeekBarPreference.
(I extended the class and do see its value, it just doesn't show anywhere or seem to affect anything)
For reference, the Support Library defines the following:
<declare-styleable name="SeekBarPreference">
<attr format="integer" name="min"/>
<attr name="android:max"/>
<attr name="android:layout"/>
<attr format="integer" name="seekBarIncrement"/>
<attr format="boolean" name="adjustable"/>
<attr format="boolean" name="showSeekBarValue"/>
</declare-styleable>
which one can see set in the SeekBarPreference class itself:
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.SeekBarPreference, defStyleAttr, defStyleRes);
mMin = a.getInt(R.styleable.SeekBarPreference_min, 0);
setMax(a.getInt(R.styleable.SeekBarPreference_android_max, 100));
setSeekBarIncrement(a.getInt(R.styleable.SeekBarPreference_seekBarIncrement, 0));
mAdjustable = a.getBoolean(R.styleable.SeekBarPreference_adjustable, true);
mShowSeekBarValue = a.getBoolean(R.styleable.SeekBarPreference_showSeekBarValue, true);
I added some math to OnPreferenceChangeListener that uses the defined increment to round the value properly.
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String key = preference.getKey();
if (key.equals(getString(R.string.key_scanning_delay))) {
final SeekBarPreference sbp = (SeekBarPreference) preference;
final int increment = sbp.getSeekBarIncrement();
float value = (int) newValue;
final int rounded = Math.round(value / increment);
final int finalValue = rounded * increment;
if (finalValue == value) return true;
else sbp.setValue(finalValue);
return false;
}
return true;
}
I'm using androidx.preference.SeekBarPreference, but there is probably little to no differences between these two libraries.
So, first check that it's the right preference. Then do the math. If the new calculated value is the same as the value the method was called with, return true so the value will be persisted. Most likely the values will differ on the first go. In that case, call the preference's setValue method again to update the view (and the value label if used). This time no math is needed (obviously) so it will return true and the value will be persisted. Finally return false so the original uncorrected value will be discarded.
In the documentation, the only xml attribute mentioned is android:thumb so what you're trying to do doesn't seem possible.
https://developer.android.com/reference/android/widget/SeekBar
I'd suggest going with either a programmatic approach or you could implement your own ViewGroup which accepts a parameter like seekBarIncrement and then passes it to the SeekBarPreference.

Cannot set visibility on individual items in a ConstraintLayout.Group

I have a ConstraintLayout.Group defined like this:
<android.support.constraint.Group
android:id="#+id/someGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="
textView1,
textView2,
button1" />
I change the visibility of this group from GONE to VISIBLE:
someGroup.visibility = VISIBLE
But when I try to override it by specifying the visibility of one of the views in that group:
button1.visibility = GONE
...it doesn't work. I log this visibility to logcat and it says 8 (GONE), but I can still see the view.
Any ideas what might be happening here? I tried calling requestLayout and updatePreLayout on this group, I tried changing the visibility a few times, visible, invisible and then gone. I even rebuilt the whole project because some stackoverflow answer said that it might help for visiblity issues in ConstraintLayout. I also tried versions 1.1.3 and 2.2.0-alpha. Nothing worked. It's always visible.
Update: The behavior of individual view visibility within a group has been change and is reported as fixed in ConstraintLayout version 2.0.0 beta 6. See bug fixes for ConstraintLayout 2.0.0 beta 6 .
You are seeing the correct behavior. When you place a view within a group, you give up the ability to change the individual view's visibility. I think the mechanics are that the view's visibility is set (by you) then the group's visibility is assigned (by the system) based upon group membership.
The workaround is dependent upon your needs:
Keep the view you want to control the visibility of out of a group;
Manage your own group logic and forget the groups offered by the system;
Manage group membership using setReferencedIds.
I think that this is a common complaint, but I also think that it is unlikely to be addressed. (IMHO)
I just commented on another question regarding this exact issue, so I have taken the liberty to post here (although the answer is already accepted) a simple class that will help manage a ConstraintLayout group.
ManagedGroup.java
/**
* Manage a ConstraintLayout Group view membership as a view's visibility is changed. Calling
* {#link #setVisibility(View, int)} will set a view's visibility and remove it from the group.
* Other methods here provide explicit means to manage a group's view membership.
* <p>
* Usage: In XML define
* <pre>{#code
* <[Package].ManagedGroup
* android:id="#+id/group"
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* android:visibility="visible"
* app:constraint_referenced_ids="id1,id2,id3..." />}
* </pre>
*/
public class ManagedGroup extends Group {
private final Set<Integer> mRemovedRefIds = new HashSet<>();
public ManagedGroup(Context context) {
super(context);
}
public ManagedGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ManagedGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* Set the reference ids for the group and clear the removed id array.
*
* #param ids All identifiers in the group.
*/
#Override
public void setReferencedIds(#NonNull int[] ids) {
super.setReferencedIds(ids);
mRemovedRefIds.clear();
}
/**
* Set visibility for view and remove the view's id from the group.
*
* #param view View for visibility change
* #param visibility View.VISIBLE, View.INVISIBLE or View.GONE.
*/
public void setVisibility(#NonNull View view, int visibility) {
removeReferencedIds(view.getId());
view.setVisibility(visibility);
}
/**
* Add all removed views back into the group.
*/
public void resetGroup() {
setReferencedIds(getAllReferencedIds());
}
/**
* Remove reference ids from the group. This is done automatically when
* setVisibility(View view, int visibility) is called.
*
* #param idsToRemove All the ids to remove from the group.
*/
public void removeReferencedIds(int... idsToRemove) {
for (int id : idsToRemove) {
mRemovedRefIds.add(id);
}
int[] refIds = getReferencedIds();
Set<Integer> newRefIdSet = new HashSet<>();
for (int id : refIds) {
if (!mRemovedRefIds.contains(id)) {
newRefIdSet.add(id);
}
}
super.setReferencedIds(copySetToIntArray(newRefIdSet));
}
/**
* Add reference ids to the group.
*
* #param idsToAdd Identifiers to add to the group.
*/
public void addReferencedIds(int... idsToAdd) {
for (int id : idsToAdd) {
mRemovedRefIds.remove(id);
}
super.setReferencedIds(joinArrays(getReferencedIds(), idsToAdd));
}
/**
* Return int[] of all ids in the group plus those removed.
*
* #return All current ids in group plus those removed.
*/
#NonNull
public int[] getAllReferencedIds() {
return joinArrays(getReferencedIds(), copySetToIntArray(mRemovedRefIds));
}
#NonNull
private int[] copySetToIntArray(Set<Integer> fromSet) {
int[] toArray = new int[fromSet.size()];
int i = 0;
for (int id : fromSet) {
toArray[i++] = id;
}
return toArray;
}
#NonNull
private int[] joinArrays(#NonNull int[] array1, #NonNull int[] array2) {
int[] joinedArray = new int[array1.length + array2.length];
System.arraycopy(array1, 0, joinedArray, 0, array1.length);
System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
return joinedArray;
}
}

Dynamically resizing a gridView

Months ago I successfully wrote code to resize a gridView to fit the amount of rows required of it each time a new table of data was to be loaded. I've just noticed that this is no longer working and I have no idea why. All that has changed as far as I can tell is that I've upgraded the SDK. Below is the code that used to dynamically resize the gridView.
/**
* create a resizable gridView by utilizing the onLayoutChanged system method
* #param items an arrayList containing the items to be read into each cell
*/
public void createGridView(ArrayList<String> items)
{
Log.d(TAG, "createGridView(): ");
gridView.setAdapter(new GridAdapter(items));
gridView.addOnLayoutChangeListener(new View.OnLayoutChangeListener(){
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
Log.d(TAG, "onLayoutChange(): ");
currentGridHeight = gridView.getHeight();
if (singleGridHeight == 0){
singleGridHeight = currentGridHeight/currentGridRows;
}
// fix for nestedScrollView automatically scrolling to the bottom after swipe
nestedScrollView.scrollTo(0, 0);
}
});
}
/**
* re-sizes the height of the gridView based on the amount of rows to be added
* #param gridView
* #param items
* #param columns
*/
private static void resizeGridView(GridView gridView, int items, int columns) {
Log.d(TAG, "resizeGridView(): ");
ViewGroup.LayoutParams params = gridView.getLayoutParams();
params.height = singleGridHeight * items;
gridView.setLayoutParams(params);
gridView.requestLayout();
}
// gridView adapter
private static final class GridAdapter extends BaseAdapter {
final ArrayList<String> mItems;
final int mCount;
/**
* Default constructor
*
* #param items to fill data to
*/
private GridAdapter(final ArrayList<String> items) {
// create arrayList full of data called "items"
// ..
// ..
// resizing the gridView based on the number of rows
currentGridRows = items.size();
// if this isn't the very first gridView then resize it to fit the rows
if (currentGridHeight != 0){
resizeGridView(gridView,items.size(),6);
}
}
When the gridView is first created (the very first table loaded) I get the measurement of the height of the gridView and divide it by the number of rows in order to determine the height required per row. When I load subsequent gridViews I resize them by multiplying this height by the number of rows.
This is no longer working. Subsequent gridViews are still roughly only big enough to hold one row.
Any ideas?
EDIT: It appears that it is calculating the singleRowHeight as 9dp when in fact each row requires about 64dp.
Try to use RecyclerView with LayoutManager like this:
recyclerView.setLayoutManager(new GridLayoutManager(context, columnsCount));

Android: Custom View with object reference from attrs.xml, always null

I'm trying to setup a relationship hierarchy between objects. Every object has a parent of the same type as itself, or null.
I have a main.xml that contains some of these:
<com.morsetable.MorseKey
android:id="#+id/bi"
android:layout_weight="1"
custom:code=".."
custom:parentKey="#id/be"
android:text="#string/i" />
a res/values/attrs.xml that contains one of these:
<declare-styleable name="MorseKey">
<attr name="code" format="string"/>
<attr name="parentKey" format="reference"/>
</declare-styleable>
and a class (that is not my activity) that contains this:
public class MorseKey extends Button {
public MorseKey(Context context, AttributeSet attrs) {
super(context, attrs);
initMorseKey(attrs);
}
private void initMorseKey(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.MorseKey);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.MorseKey_code:
code = a.getString(attr);
break;
case R.styleable.MorseKey_parentKey:
parent = (MorseKey)findViewById(a.getResourceId(attr, -1));
//parent = (MorseKey)findViewById(R.id.be);
Log.d("parent, N:", ""+parent+","+N);
break;
}
}
a.recycle();
}
private MorseKey parent;
private String code;
}
This isn't working. Every MorseKey instance reports N == 2 (good) and parent == null (bad). More, parent == null even when I explicitly try setting it to some arbitrary value (see comment). I have also tried custom:parentKey="#+id/be" (with the plus sign) but that didn't work either. What am I doing wrong?
If your MorseKey class is in a separate java file, which I assume is the case from your statment "a class (that is not my activity)". Then I believe the problem is in your use of findViewById(). findViewById() will look for a resource within the MorseKey view itself rather than the main.xml file.
Maybe try getting the parent of the MorseKey instance and calling parent.findViewById().
case R.styleable.MorseKey_parentKey:
parent = this.getParent().findViewById(a.getResourceId(attr, -1));
Though this will only work if your MorseKey parent and child are in the same layout.
<LinearLayout ...>
<MorseKey ..../><!-- parent -->
<MorseKey ..../><!-- child -->
</LinearLayout>
But it would be quite difficult to find the view if your layout is something like this with the parent and child in separate layouts.
<LinearLayout ...>
<MorseKey ..../><!-- parent -->
</LinearLayout>
<LinearLayout ...>
<MorseKey ..../><!-- child -->
</LinearLayout>

Categories

Resources