My app contains two ExpandableHeightListViews and one ExpandableHeightGridView with shared vertical scrolling.
The contents of the views are read in from a file. This works fine with small files, the data is displayed correctly and the activity loads.
However, when selecting large files, the activity never actually loads, when I debug it, the adapters for the three views are just looping round endlessly. I'm assuming this is due to the expanded nature of the views they are trying to load everything, but surely this can be avoided?
Here is the code ...
ExpandableHeightListView ...
public class ExpandableHeightListView extends ListView
{
boolean expanded = false;
public ExpandableHeightListView(Context context)
{
super(context);
}
public ExpandableHeightListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ExpandableHeightListView(Context context, AttributeSet attrs, int defaultStyle)
{
super(context, attrs, defaultStyle);
}
public boolean isExpanded()
{
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// HACK! TAKE THAT ANDROID!
if (isExpanded())
{
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
else
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
}
ExpandableHeightGridView ...
public class ExpandableHeightGridView extends GridView
{
boolean expanded = false;
public ExpandableHeightGridView(Context context)
{
super(context);
}
public ExpandableHeightGridView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ExpandableHeightGridView(Context context, AttributeSet attrs,
int defStyle)
{
super(context, attrs, defStyle);
}
public boolean isExpanded()
{
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// HACK! TAKE THAT ANDROID!
if (isExpanded())
{
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
else
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
}
MainActivity ...
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView (R.layout.activity_editor);
Intent intent = getIntent();
m_File = new File (intent.getStringExtra ("file_path"));
m_FileLength = (int) m_File.length();
// Account for non-complete line at end of file ...
if ((m_FileLength % 8) == 0)
m_NumLines = (m_FileLength / 8) + ADDITIONAL_LINES;
else
m_NumLines = (m_FileLength / 8) + ADDITIONAL_LINES + 1;
// Set up index ListView
m_IndexListView = (ExpandableHeightListView) findViewById (R.id.index_listview);
m_IndexAdapter = new IndexAdapter (getApplicationContext(), m_NumLines);
m_IndexListView.setAdapter (m_IndexAdapter);
m_IndexListView.setExpanded(true);
// File reader object for hex/ascii adapters ...
FileReader fileReader = new FileReader (getApplicationContext(), m_File);
// Set up hex GridView
m_HexGridView = (ExpandableHeightGridView) findViewById (R.id.hex_gridview);
m_HexAdapter = new HexAdapter (getApplicationContext(), fileReader);
m_HexGridView.setAdapter(m_HexAdapter);
m_HexGridView.setExpanded (true);
// Set up ascii ListView
m_AsciiListView = (ExpandableHeightListView) findViewById (R.id.ascii_listview);
m_AsciiAdapter = new AsciiAdapter (getApplicationContext(), m_NumLines, fileReader);
m_AsciiListView.setAdapter(m_AsciiAdapter);
m_AsciiListView.setExpanded (true);
m_HexGridView.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
view.setSelected (true);
}
});
The three adapters simply read some data from the given file and display it accordingly.
Does anyone know how to fix this and make the adapter views only load the necessary part?
EDIT: Adapter code ...
HexAdapter ...
public class HexAdapter extends BaseAdapter
{
// CONSTANTS
// Number of additional blank lines to append ...
private final int ADDITIONAL_LINES = 30;
// VARIABLES
// Context of the EditorActivity ...
private Context m_Context;
// Reference to file being edited ...
private FileReader m_FileReader;
// Number of elements that are within file ...
private int m_FileLength;
// Number of total elements to produce ...
private int m_NumElements;
// FUNCTIONS
public HexAdapter (Context c, FileReader fileReader)
{
m_Context = c;
m_FileReader = fileReader;
m_FileLength = (int) m_FileReader.getLength();
int finishLine = 8 - (m_FileLength % 8);
m_NumElements = m_FileLength + finishLine + (ADDITIONAL_LINES * 8);
}
public int getCount()
{
return m_NumElements;
}
public Object getItem(int position)
{
return null;
}
public long getItemId(int position)
{
return 0;
}
// Create new TextView for each item referenced by the Adapter ...
public View getView(int position, View convertView, ViewGroup parent)
{
LayoutInflater inflater = (LayoutInflater)
m_Context.getSystemService (Context.LAYOUT_INFLATER_SERVICE);
View view;
if (null == convertView)
view = inflater.inflate (R.layout.editor_hex_element, parent, false);
else
view = convertView;
// Remove padding if rightmost element ...
LinearLayout container = (LinearLayout) view.findViewById (R.id.gridview_hex_container);
if ((position % 8) == 7)
container.setPadding (0,0,0,0);
else
container.setPadding (0,0,1,0);
String hexText;
// If position is within current file space, read from file ...
if (position < m_FileLength)
{
// Read from file and generate hexadecimal text
byte[] buffer = m_FileReader.ReadBytesFromFile(position, 1);
hexText = Integer.toHexString ((char)buffer[0]).toUpperCase();
// Prepend extra zero if length is 1 ...
if (hexText.length() == 1)
hexText = "0" + hexText;
}
else
{
// Set to default un-edited value (00) ...
hexText = "00";
}
// Get TextView instance and set text ...
TextView textView = (TextView) view.findViewById (R.id.gridview_hex_edit);
textView.setText (hexText);
// Set colour depending on file length ...
if (position < m_FileLength)
textView.setTextColor (m_Context.getResources ().getColor (R.color.DarkerAccentOrange));
else
textView.setTextColor (m_Context.getResources ().getColor (R.color.hex_gray));
return view;
}
// TODO: Add function to update file length and numElements variables
// public void UpdateFileLength (int numLength) ...
}
AsciiAdapter ...
public AsciiAdapter (Context c, int numLines, FileReader fileReader)
{
m_Context = c;
m_FileReader = fileReader;
m_NumLines = numLines;
m_FileLength = (int) fileReader.getLength();
}
public int getCount()
{
return m_NumLines;
}
public Object getItem(int position)
{
return null;
}
public long getItemId(int position)
{
return 0;
}
// Create new TextView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent)
{
LayoutInflater inflater = (LayoutInflater)
m_Context.getSystemService (Context.LAYOUT_INFLATER_SERVICE);
View view;
if (null == convertView)
view = inflater.inflate (R.layout.editor_ascii_element, parent, false);
else
view = convertView;
final int readPosition = position * BYTE_SIZE;
final int fileRemaining = m_FileLength - readPosition;
final int readLength = (fileRemaining >= BYTE_SIZE) ? BYTE_SIZE : fileRemaining;
byte[] buffer;
StringBuilder builder = new StringBuilder ();
// If some of file remaining, read ...
if (readLength > 0)
{
buffer = m_FileReader.ReadBytesFromFile (readPosition, readLength);
// Build read string ...
for (byte b : buffer)
builder.append (convertByte (b));
}
// Append remaining part of string (not read from file) ...
final int remaining = (readLength > 0) ? (BYTE_SIZE - readLength) : BYTE_SIZE;
for (int k = 0; k < remaining; k++)
builder.append ('.');
TextView textView = (TextView) view.findViewById (R.id.ascii_textview);
textView.setText (builder.toString());
return view;
}
}
Related
I have a page in viewpager which have a button and a few views which are hidden by default. On click of the button, the views are to be toggled(Show/Hide). My issue is button click is working, but viewpager's height does not changes.
This is the code for adapter:
public class HomeCategoryAdapter extends PagerAdapter implements View.OnClickListener {
private LayoutInflater inflater;
private View lin_view_more, lin_view_less, lin_view_1, lin_view_2, lin_view_3, lin_view_4;
private WrapContentViewPager pager;
public HomeCategoryAdapter(Context context) {
this.mContext = context;
this.inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return 5;
}
#Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
pager = (EnhancedWrapContentViewPager) container;
View itemView = inflater.inflate(R.layout.homescreen_category_adapter, container, false);
...
lin_view_more = itemView.findViewById(R.id.lin_view_more);
lin_view_more.setOnClickListener(this);
lin_view_1 = itemView.findViewById(R.id.lin_view_1);
lin_view_1.setOnClickListener(this);
lin_view_2 = itemView.findViewById(R.id.lin_view_2);
lin_view_2.setOnClickListener(this);
lin_view_3 = itemView.findViewById(R.id.lin_view_3);
lin_view_3.setOnClickListener(this);
lin_view_4 = itemView.findViewById(R.id.lin_view_4);
lin_view_4.setOnClickListener(this);
switch (position) {
case 0:
parent_1.setVisibility(View.VISIBLE);
parent_2.setVisibility(View.GONE);
parent_3.setVisibility(View.GONE);
parent_4.setVisibility(View.GONE);
parent_5.setVisibility(View.GONE);
break;
case 1:
parent_1.setVisibility(View.GONE);
parent_2.setVisibility(View.VISIBLE);
parent_3.setVisibility(View.GONE);
parent_4.setVisibility(View.GONE);
parent_5.setVisibility(View.GONE);
break;
case 2:
parent_1.setVisibility(View.GONE);
parent_2.setVisibility(View.GONE);
parent_3.setVisibility(View.VISIBLE);
parent_4.setVisibility(View.GONE);
parent_5.setVisibility(View.GONE);
break;
case 3:
parent_1.setVisibility(View.GONE);
parent_2.setVisibility(View.GONE);
parent_3.setVisibility(View.GONE);
parent_4.setVisibility(View.VISIBLE);
parent_5.setVisibility(View.GONE);
break;
case 4:
parent_1.setVisibility(View.GONE);
parent_2.setVisibility(View.GONE);
parent_3.setVisibility(View.GONE);
parent_4.setVisibility(View.GONE);
parent_5.setVisibility(View.VISIBLE);
break;
}
...
((WrapContentViewPager) container).addView(itemView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((WrapContentViewPager) container).removeView((View) object);
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.lin_view_more:
lin_view_more.setVisibility(View.GONE);
lin_view_1.setVisibility(View.VISIBLE);
lin_view_2.setVisibility(View.VISIBLE);
lin_view_3.setVisibility(View.VISIBLE);
lin_view_4.setVisibility(View.VISIBLE);
lin_view_less.setVisibility(View.VISIBLE);
if (pager != null) {
pager.measureView();
}
break;
case R.id.lin_view_less:
lin_view_more.setVisibility(View.VISIBLE);
lin_view_1.setVisibility(View.GONE);
lin_view_2.setVisibility(View.GONE);
lin_view_3.setVisibility(View.GONE);
lin_view_4.setVisibility(View.GONE);
lin_view_less.setVisibility(View.GONE);
if (pager != null) {
pager.post(new Runnable() {
#Override
public void run() {
pager.measureView();
}
});
}
break;
}
}
}
This is the code for modified view pager to make it wrap_content:
public class WrapContentViewPager extends ViewPager {
public WrapContentViewPager (Context context) {
super(context);
}
public WrapContentViewPager (Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int childMeasuredHeight = child.getMeasuredHeight();
if (childMeasuredHeight > height) {
Log.debugMessage("WrapContentViewPager", "page height updated");
height = childMeasuredHeight;
}
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void measureView(){
requestLayout();
}
}
As shown in onClick above, I am calling requestLayout() on pager. The page height gets calculated but the layout is not expanding.
Update:
If I swipe to another page(3rd or 4th) then go back to this page, show/hide are working fine.
Not getting why is it not working.
Can you add this code after requestLayout();code line:
invalidate();
if it doesn't work also can you try this:
postInvalidate();
For what these code lines do, you can take a look at this:
What does postInvalidate() do?
I have the background of the GridView which is Transparent to its main layout. The List Item Should also have the background transparent , but the Seperator should have different Color .How can this be Achieved?
Main Container(image drawable is setted through progamatically)
<*************.AutoGridView
android:id="#+id/dashMenu"
android:layout_width="match_parent"
android:layout_height="210dp"
android:layout_alignParentBottom="true"
android:background="#android:color/transparent"
android:horizontalSpacing="1dp"
android:numColumns="#integer/grid_columns"
android:scrollbars="none"
android:stretchMode="columnWidth"
android:verticalSpacing="1dp"
android:visibility="visible" />
items
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/menuWrapper"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="12dp"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="#+id/menuImg"
android:layout_width="58dp"
android:layout_height="58dp"
android:padding="8dp"
android:src="#mipmap/ic_launcher" />
<TextView
android:id="#+id/menuLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="#font/montserrat_regular"
android:gravity="center_horizontal"
android:paddingBottom="6dp"
android:text="Menu Item"
android:textColor="#color/white"
android:textSize="12sp" />
</LinearLayout>
</RelativeLayout>
Adapter
public class HomeMenuAdapter extends BaseAdapter {
private Context mContext;
private ArrayList<Menu> menus;
public HomeMenuAdapter(Context context, ArrayList<Menu> menus) {
mContext = context;
this.menus = menus;
}
#Override
public int getCount() {
return menus.size();
}
#Override
public Menu getItem(int position) {
return menus.get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
Menu menu = menus.get(position);
ImageView menuImage;
TextView menuLabel;
View v;
if (convertView == null) {
v = layoutInflater.inflate(R.layout.dash_menu_item, null);
} else {
v = convertView;
}
/*int columns = 3;
int total = menus.size();
int rows = total / columns;
LinearLayout menuWrapper = v.findViewById(R.id.menuWrapper);
if (((position + 1) % columns) == 0) {
if((total - position) > columns ) {
CommonUtils.setDrawableBackground(mContext, menuWrapper, R.drawable.menu_border_bottom);
} else {
CommonUtils.setDrawableBackground(mContext, menuWrapper, R.drawable.menu_border_neutral);
}
} else if((total - position) > columns ) {
CommonUtils.setDrawableBackground(mContext, menuWrapper, R.drawable.menu_border_right_bottom);
} else {
CommonUtils.setDrawableBackground(mContext, menuWrapper, R.drawable.menu_border_right);
}
*/
menuImage = v.findViewById(R.id.menuImg);
menuLabel = v.findViewById(R.id.menuLabel);
if(menu.getIcon() != null && !TextUtils.isEmpty(menu.getIcon()) &&
URLUtil.isValidUrl(menu.getIcon())) {
RequestOptions requestOptions = new RequestOptions();
requestOptions.error(menu.getIconId());
Glide.with(menuImage).applyDefaultRequestOptions(requestOptions)
.load(menu.getIcon()).into(menuImage);
} else if(menu.getIconId() != 0) {
setImage(menuImage, menu.getIconId());
}
menuLabel.setText(menu.getName());
v.setOnClickListener(new OnOneClickListener() {
#Override
public void onOneClick(View v) {
new Router(mContext).route(menu);
}
});
return v;
}
private void setImage(ImageView menuImage, int iconId) {
menuImage.setImageDrawable(mContext.getResources().getDrawable(
iconId));
}
}
AutoGridView
public class AutoGridView extends GridView {
private static final String TAG = "AutoGridView";
private int numColumnsID;
private int previousFirstVisible;
private int numColumns = 1;
public AutoGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
public AutoGridView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public AutoGridView(Context context) {
super(context);
}
/**
* Sets the numColumns based on the attributeset
*/
private void init(AttributeSet attrs) {
// Read numColumns out of the AttributeSet
int count = attrs.getAttributeCount();
if(count > 0) {
for(int i = 0; i < count; i++) {
String name = attrs.getAttributeName(i);
if(name != null && name.equals("numColumns")) {
// Update columns
this.numColumnsID = attrs.getAttributeResourceValue(i, 1);
updateColumns();
break;
}
}
}
Log.d(TAG, "numColumns set to: " + numColumns);
}
/**
* Reads the amount of columns from the resource file and
* updates the "numColumns" variable
*/
private void updateColumns() {
this.numColumns = getContext().getResources().getInteger(numColumnsID);
}
#Override
public void setNumColumns(int numColumns) {
this.numColumns = numColumns;
super.setNumColumns(numColumns);
Log.d(TAG, "setSelection --> " + previousFirstVisible);
setSelection(previousFirstVisible);
}
#Override
protected void onLayout(boolean changed, int leftPos, int topPos, int rightPos, int bottomPos) {
super.onLayout(changed, leftPos, topPos, rightPos, bottomPos);
setHeights();
}
#Override
protected void onConfigurationChanged(Configuration newConfig) {
updateColumns();
setNumColumns(this.numColumns);
}
#Override
protected void onScrollChanged(int newHorizontal, int newVertical, int oldHorizontal, int oldVertical) {
// Check if the first visible position has changed due to this scroll
int firstVisible = getFirstVisiblePosition();
if(previousFirstVisible != firstVisible) {
// Update position, and update heights
previousFirstVisible = firstVisible;
setHeights();
}
super.onScrollChanged(newHorizontal, newVertical, oldHorizontal, oldVertical);
}
/**
* Sets the height of each view in a row equal to the height of the tallest view in this row.
*/
private void setHeights() {
ListAdapter adapter = getAdapter();
if(adapter != null) {
for(int i = 0; i < getChildCount(); i+=numColumns) {
// Determine the maximum height for this row
int maxHeight = 0;
for(int j = i; j < i+numColumns; j++) {
View view = getChildAt(j);
if(view != null && view.getHeight() > maxHeight) {
maxHeight = view.getHeight();
}
}
//Log.d(TAG, "Max height for row #" + i/numColumns + ": " + maxHeight);
// Set max height for each element in this row
if(maxHeight > 0) {
for(int j = i; j < i+numColumns; j++) {
View view = getChildAt(j);
if(view != null && view.getHeight() != maxHeight) {
view.setMinimumHeight(maxHeight);
}
}
}
}
}
}
}
Do not Suggest adding extra view(please).
Please refer to
How can a divider line be added in an Android RecyclerView?
You have to change the item decorator color as u desire
I just followed this tutorial, to create a custom View as an item of a GridLayout.
That's my CustomView
public class RowView extends View{
boolean touchOn;
boolean mDownTouch = false;
private OnToggledListener toggledListener;
int _IdRow = 0;
int _IdColumn = 0;
public RowView(Context context, int Rows, int Columns) {
super(context);
this._IdRow = Rows;
this._IdColumn = Columns;
init();
}
public RowView(Context context) {
super(context);
init();
}
public RowView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RowView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
touchOn = false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
#Override
protected void onDraw(Canvas canvas) {
if (touchOn) {
canvas.drawColor(Color.RED);
} else {
canvas.drawColor(Color.GRAY);
}
}
//onClick not possible to use on custom View so, onTouchEvent is the solution
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
//if Click
case MotionEvent.ACTION_DOWN:
touchOn = !touchOn;
invalidate();
if(toggledListener != null){
toggledListener.OnToggled(this, touchOn);
}
mDownTouch = true;
return true;
case MotionEvent.ACTION_UP:
if (mDownTouch) {
mDownTouch = false;
performClick();
return true;
}
}
return false;
}
#Override
public boolean performClick() {
super.performClick();
return true;
}
public void setOnToggledListener(OnToggledListener listener){
toggledListener = listener;
}
public int get_IdRow() {
return _IdRow;
}
public int get_IdColumn() {
return _IdColumn;
}
On this class I can detect when user clicks on an item of GridLayout and change it to another color, that's ok.
But the problem comes at the time to create this :
This is my MainActivity where I show the GridLayout :
int numOfCol = mGridLayout.getColumnCount();
int numOfRow = mGridLayout.getRowCount();
mRowViews = new RowView[numOfCol*numOfRow];
for(int yPos=0; yPos<numOfRow; yPos++){
for(int xPos=0; xPos<numOfCol; xPos++){
RowView tView = new RowView(this, xPos, yPos);
tView.setOnToggledListener(this);
mRowViews[yPos*numOfCol + xPos] = tView;
mGridLayout.addView(tView);
}
}
mGridLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){
#Override
public void onGlobalLayout() {
final int MARGIN = 5;
int pWidth = mGridLayout.getWidth();
int pHeight = mGridLayout.getHeight();
int numOfCol = mGridLayout.getColumnCount();
int numOfRow = mGridLayout.getRowCount();
int w = pWidth/numOfCol;
int h = pHeight/numOfRow;
for(int yPos=0; yPos<numOfRow; yPos++){
for(int xPos=0; xPos<numOfCol; xPos++){
GridLayout.LayoutParams params =
(GridLayout.LayoutParams)mRowViews[yPos*numOfCol + xPos].getLayoutParams();
params.width = w - 2*MARGIN;
params.height = h - 2*MARGIN;
params.setMargins(MARGIN, MARGIN, MARGIN, MARGIN);
mRowViews[yPos*numOfCol + xPos].setLayoutParams(params);
}
}
}});
Also there is a method of the Interface OnToggledListener that gives to me the row and column of my GridLayout when an item of it is clicked :
#Override
public void OnToggled(MyView v, boolean touchOn) {
//get the id string
String idString = v.get_IdRow() + ":" + v.get_IdColumn();
}
I'd like to avoid to create that mGridLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() because it fills on the screen thing that I don't want... I tried to put GridLayout 6x6 with android:layout_height="400dp" and it only show 3x3 and this is the LogCat message
D/android.widget.GridLayout: vertical constraints: y6-y0>=1749, y6-y5<=291, y5-y4<=291, y4-y3<=291, y3-y2<=291, y2-y1<=291, y1-y0<=291 are inconsistent; permanently removing: y6-y5<=291.
I'd like to do something like GridLayout[row][colum] to get the color of background and then do stuff, but I'm not able to find this solution.
For simplifying, you can implement a custom Board view wrapping the GridLayout and related logic. Below I report a possible approach.
Expectation here is to have an ItemView for representing one single cell in the board.
public class Board extends FrameLayout implements View.OnClickListener {
private GridLayout mGridView;
private int mRowsCount;
private int mColsCount;
private int mCellSpace;
private OnItemClickListener mOnItemClickListener;
public Board(Context context) {
super(context);
init(context, null);
}
// other constructors
private void init(Context context, AttributeSet attrs) {
// default values
mRowsCount = 1;
mColsCount = 1;
View layout = inflate(getContext(), R.layout.view_lights_board, null);
mGridView = (GridLayout) layout.findViewById(R.id.view_grid);
mGridView.setRowCount(mRowsCount);
mGridView.setColumnCount(mColsCount);
mGridView.post(new Runnable() {
#Override
public void run() {
int width = getMeasuredWidth() / getColumnsCount();
int height = getMeasuredHeight() / getRowsCount();
for (int i = 0; i < getRowsCount(); i++) {
for (int j = 0; j < getColumnsCount(); j++) {
GridLayout.LayoutParams params = (GridLayout.LayoutParams)
getChildAt(i, j).getLayoutParams();
params.width = width;
params.height = height;
getChildAt(i, j).setLayoutParams(params);
}
}
}
});
addView(layout);
}
// this method allows to dinamically create grid
public void buildChildren(int rowsCount, int colsCount) {
mRowsCount = rowsCount;
mColsCount = colsCount;
mGridView.setRowCount(mRowsCount);
mGridView.setColumnCount(mColsCount);
buildChildren();
}
public void buildChildren() {
for (int i = 0; i < getRowsCount(); i++) {
for (int j = 0; j < getColumnsCount(); j++) {
ItemView view = new ItemView(getContext(), i, j);
view.setOnClickListener(this);
mGridView.addView(view);
}
}
}
public void setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
}
public ItemView getChildAt(int rowIndex, int columnIndex) {
int index = (getColumnsCount() * rowIndex) + columnIndex;
return (ItemView) mGridView.getChildAt(index);
}
public boolean isTouchOn(int rowIndex, int columnIndex) {
return getChildAt(rowIndex, columnIndex).isTouchOn();
}
public int getColumnsCount() {
return mGridView.getColumnCount();
}
public int getRowsCount() {
return mGridView.getRowCount();
}
#Override
public void onClick(View v) {
if (v instanceof ItemView) {
ItemView view = (ItemView) v;
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(view);
}
}
}
public interface OnItemClickListener {
void onItemClick(ItemView view);
}
}
In your Activity layout you will have something like this (here I assume your app package is com.android.example):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.android.example.Board
android:id="#+id/grid"
android:layout_width="match_parent"
android:layout_height="400dp" />
</FrameLayout>
And this is possible implementation of the Activity:
public class MainActivity extends AppCompatActivity implements LightsOutBoard.OnItemClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Board board = (Board) findViewById(R.id.grid);
board.setOnItemClickListener(this);
board.buildChildren(3, 3);
}
#Override
public void onItemClick(ItemView view) {
String text = view.getRowIndex() + " - " + view.getColumnIndex();
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
}
Hope this could help.
Trying to build a simple grid with views I create. The views will look like dominos this is the class that defines them that extends view
public class Domino extends View{
private Paint paint;
public Domino(Context context) {
super(context);
init();
}
public void init(){
paint = new Paint();
paint.setTextSize(12);
paint.setColor(0xFF668800);
paint.setStyle(Paint.Style.FILL);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
invalidate();
}
}
And then in an array adapter I try to build them like this
private class CustomAdapter extends ArrayAdapter<String> {
private Context mContext;
private int tileW, tileH;
private List<String> list = new ArrayList<String>();
public CustomAdapter(Context context, int textViewResourceId, List<String> objects) {
super(context, textViewResourceId, objects);
this.mContext = context;
this.list = objects;
// we need to do some calculation to get accurate screen dimensions if we're going fullscreen
DisplayMetrics displayMetrics = new DisplayMetrics();
((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(displayMetrics);
this.tileW = displayMetrics.widthPixels / 4;
this.tileH = tileW/2;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Domino domino;
if (convertView == null) {
// if it's not recycled, initialize some attributes
domino = new Domino(mContext);
domino.setLayoutParams(new GridView.LayoutParams(this.tileW, this.tileH));
domino.measure(this.tileW, this.tileH);
}
else {
domino = (Domino) convertView;
}
String colorString = list.get(position);
int rid = 0;
// figure out what color we're going to use
if (colorString.equals("r")) {
rid = R.drawable.grid_red;
}
else if (colorString.equals("o")) {
rid = R.drawable.grid_orange;
}
else if (colorString.equals("y")) {
rid = R.drawable.grid_yellow;
}
else if (colorString.equals("g")) {
rid = R.drawable.grid_green;
}
else if (colorString.equals("b")) {
rid = R.drawable.grid_blue;
}
else if (colorString.equals("i")) {
rid = R.drawable.grid_indigo;
}
else if (colorString.equals("v")) {
rid = R.drawable.grid_violet;
}
else {
rid = R.color.black;
}
return domino;
}
}
But I dont see anything, I want to programmatically set the height and width in the array adapter. What am i missing?
// Replace this piece of code see what happens.
if (convertView == null) {
// if it's not recycled, initialize some attributes
domino = new Domino(mContext);
domino.setLayoutParams(new GridView.LayoutParams(this.tileW, this.tileH));
domino.measure(this.tileW, this.tileH);
convertView.setTag(domino);
} else {
domino = (Domino) convertView.getTag();
}
i'm trying to place a listview inside a listviewitem. the inner listview should not be scrollable but take all size it needs to display all it's rows. is there a better way to to this? table, grid, ...? the problem i'm facing right now is that the inner listview doesn't take the space it needs, so it's cut at about the end of the first listitem. if i try to scroll, just the outer listview is scrolling which is exactly what i want.
thanks, my final solution is
LinearLayout layout = (LinearLayout) row.findViewById(R.id.LLBroadcasts);
layout.removeAllViews();
for (Item b : bs.getItems()) {
View child = _inflater.inflate(R.layout.item_row, null);
TextView tvTitle = (TextView) child.findViewById(R.id.TVItemTitle);
tvTitle.setText(b.getTitle());
TextView tvDesc = (TextView) child.findViewById(R.id.TVItemDescription);
tvDesc.setText(b.getDescription());
layout.addView(child);
}
From the Android documentation - Listview: ListView is a view group that displays a list of scrollable items
You do not really want to scroll that inner list view, you want to scroll the outer listview. However I asume that the inner listview may vary on the amount of elements it contains.
Instead of the inner list view you could use a
linear layout, see this tutorial or look at Adding content to a linear layout dynamically?
table layout
For the linear layout (some sample code):
// access your linear layout
LinearLayout layout = (LinearLayout)findViewById(R.id.layout);
// load the xml structure of your row
View child = getLayoutInflater().inflate(R.layout.row);
// now fill the row as you would do with listview
//e.g. (TextView) child.findViewById(...
...
// and than add it
layout.addView(child);
You should save the linear layout in a view holder (see View Holder pattern). I think the removeAllViews() is only necessary when the current row has lesser inner rows than the reused one, so I would also save the number of rows in the view holder.
If the maximum number of inner rows is not to high you could also think about caching them in the view holder to avoid the inflate and findByViewId (lets say in an ArrayList).
I have the same problem in my App but I needed to use a ListView cause it was a shared item and I didn't want to replicate equal components. So.. I just fixed the size of inner ListView programatically to show all rows and.. voila! Problem solved:
ViewGroup.LayoutParams layoutParams = innerListView.getLayoutParams();
layoutParams.height = (int) context.getResources().getDimension(R.dimen.rowheight) * innerListView.getCount();
innerListView.setLayoutParams(layoutParams);
CustomAdapter adapter = new CustomAdapter(context, blabla..);
innerListView.setAdapter(adapter);
rowListView.invalidate();
Maybe somebody will find my solution useful.
It is based on #ChrLipp answer and uses LinearLayout.
public class NotScrollableListView extends LinearLayout {
private ListAdapter adapter;
private DataChangeObserver dataChangeObserver;
private Drawable divider;
private int dividerHeight;
private List<View> reusableViews = new ArrayList<>();
public NotScrollableListView(Context context) {
super(context);
}
public NotScrollableListView(Context context, AttributeSet attrs) {
super(context, attrs);
setAttributes(attrs);
}
public NotScrollableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setAttributes(attrs);
}
public ListAdapter getAdapter() {
return adapter;
}
public void setAdapter(ListAdapter adapter) {
if (this.adapter != null && dataChangeObserver != null) {
this.adapter.unregisterDataSetObserver(dataChangeObserver);
}
this.adapter = adapter;
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (adapter != null) {
dataChangeObserver = new DataChangeObserver();
adapter.registerDataSetObserver(dataChangeObserver);
fillContents();
}
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (adapter != null) {
adapter.unregisterDataSetObserver(dataChangeObserver);
dataChangeObserver = null;
}
}
private void fillContents() {
// clearing contents
this.removeAllViews();
final int count = adapter.getCount(); // item count
final int reusableCount = reusableViews.size(); // count of cached reusable views
// calculating of divider properties
ViewGroup.LayoutParams dividerLayoutParams = null;
if (divider != null && dividerHeight > 0) {
dividerLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dividerHeight);
}
// adding items
for (int i = 0; i < count; i++) {
// adding item
View converView = null;
if (i < reusableCount) { // we have cached view
converView = reusableViews.get(i);
}
View view = adapter.getView(i, converView, this);
if (i >= reusableCount) { // caching view
reusableViews.add(view);
}
addView(view);
// adding divider
if (divider != null && dividerHeight > 0) {
if (i < count - 1) {
ImageView dividerView = new ImageView(getContext());
dividerView.setImageDrawable(divider);
dividerView.setLayoutParams(dividerLayoutParams);
addView(dividerView);
}
}
}
}
private void setAttributes(AttributeSet attributes) {
int[] dividerAttrs = new int[]{android.R.attr.divider, android.R.attr.dividerHeight};
TypedArray a = getContext().obtainStyledAttributes(attributes, dividerAttrs);
try {
divider = a.getDrawable(0);
dividerHeight = a.getDimensionPixelSize(1, 0);
} finally {
a.recycle();
}
setOrientation(VERTICAL);
}
private class DataChangeObserver extends DataSetObserver {
#Override
public void onChanged() {
super.onChanged();
fillContents();
}
#Override
public void onInvalidated() {
super.onInvalidated();
fillContents();
}
}
}
<com.sample.ui.view.NotScrollableListView
android:id="#+id/internalList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#color/list_divider_color"
android:dividerHeight="#dimen/list_divider_width"
/>
I tried making this exact structure (a ListView inside of a ListView) and had the same problem of it only showing the first item of the inner ListView. I fixed it by changing the layout_height of the inner list from match_parent to a set dp.
It seemed to work exactly as I wanted it to.
#Try this nested class
this works for scroll listView inside listView Or 2 listviews in same activity
<com.example.taskgrptaskslistview.NestedListView
android:id="#+id/listviewTasks"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_weight="1"
android:cacheColorHint="#00000000" >
</com.example.taskgrptaskslistview.NestedListView>
</LinearLayout>
NestedListView :
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListAdapter;
import android.widget.ListView;
public class NestedListView extends ListView implements OnTouchListener, OnScrollListener {
private int listViewTouchAction;
private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;
public NestedListView(Context context, AttributeSet attrs) {
super(context, attrs);
listViewTouchAction = -1;
setOnScrollListener(this);
setOnTouchListener(this);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, -1);
}
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newHeight = 0;
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
ListAdapter listAdapter = getAdapter();
if (listAdapter != null && !listAdapter.isEmpty()) {
int listPosition = 0;
for (listPosition = 0; listPosition < listAdapter.getCount()
&& listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
View listItem = listAdapter.getView(listPosition, null, this);
//now it will not throw a NPE if listItem is a ViewGroup instance
if (listItem instanceof ViewGroup) {
listItem.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
listItem.measure(widthMeasureSpec, heightMeasureSpec);
newHeight += listItem.getMeasuredHeight();
}
newHeight += getDividerHeight() * listPosition;
}
if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
if (newHeight > heightSize) {
newHeight = heightSize;
}
}
} else {
newHeight = getMeasuredHeight();
}
setMeasuredDimension(getMeasuredWidth(), newHeight);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, 1);
}
}
return false;
}
}