I have been struggling to design a dialpad view like this
in portrate and
lanscape view
The view is to be created such that, in the MainActivity.java, i have
DialPadView myDialPad = new DialPadView(context);
setContentView(myDialPad);
All the images have been provided and when the user clicks on a button, it has to change some way.
I have in res/drawable/dialpad0.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_dialpad_0_pink" android:state_pressed="true" android:state_selected="true"/>
<item android:drawable="@drawable/ic_dialpad_0_blue_dark" android:state_focused="true" />
<item android:drawable="@drawable/ic_dialpad_0_blue" />
</selector>
I have specified the layout components in res/layout/dialpad_view.xml as
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageButton
android:id="@+id/dialpad_1"
style="@style/dialpadStyle"
android:contentDescription="@string/dialpad_1"
android:background="@drawable/dialpad1" />
<ImageButton
android:id="@+id/dialpad_2"
style="@style/dialpadStyle"
android:contentDescription="@string/dialpad_2"
android:background="@drawable/dialpad2" />
<ImageButton
android:id="@+id/dialpad_3"
style="@style/dialpadStyle"
android:contentDescription="@string/dialpad_3"
android:background="@drawable/dialpad3" />
...
// others
...
<ImageButton
android:id="@+id/dialpad_star"
style="@style/dialpadStyle"
android:contentDescription="@string/dialpad_star"
android:background="@drawable/dialpadstar" />
<ImageButton
android:id="@+id/dialpad_0"
style="@style/dialpadStyle"
android:contentDescription="@string/dialpad_0"
android:background="@drawable/dialpad0" />
<ImageButton
android:id="@+id/dialpad_pound"
style="@style/dialpadStyle"
android:contentDescription="@string/dialpad_pound"
android:background="@drawable/dialpadpound" />
</merge>
where @string/dialpadStyleis specified as (inside res/values/styles.xml)
<style name="dialpadStyle" parent="Widget.AppCompat.ImageButton">
<item name="android:focusableInTouchMode">true</item>
<item name="android:focusable">true</item>
<item name="android:visible">true</item>
<item name="android:height">@dimen/dialpadHeight</item> // 150dp
<item name="android:width">@dimen/dialpadWidth</item> // 150dp
</style>
This is how DialPadView.java looks like
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.ImageButton;
import android.widget.TableLayout;
import java.util.ArrayList;
import java.util.List;
import mypackage.R;
public class DialPadView extends TableLayout {
private ImageButton dialpad;
public DialPadView(final Context context){
super(context);
init(context, null);
}
public DialPadView(final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(final Context context, @Nullable final AttributeSet attributeSet) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
try {
inflater.inflate(R.layout.dialpad_view, this, true);
} catch (Exception e) {
Log.e("LayoutInflationError", e.getMessage());
}
List<ImageButton> buttons = new ArrayList<>();
int index = 0;
while (getChildAt(index) != null) {
ImageButton button = (ImageButton) getChildAt(index);
buttons.add(button);
index++;
}
dialpad = (ImageButton) buttons.get(11); // just for experimenting
Log.i("Buttons", "" + buttons.size()); // gives Buttons 12
}
}
My MainActivity.java looks like this
package myPackage;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import myPackage.customView.DialPadView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DialPadView dialpad = new DialPadView(this);
setContentView(dialpad);
}
}
I would guess that i have not implemented my DialPadView.java class well or where is the problem. Any tips / help would be appreciated.
After the comments i have modified the init function to look like this
private void init(final Context context, @Nullable final AttributeSet attributeSet) {
setStretchAllColumns(true);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
try {
inflater.inflate(R.layout.dialpad_view, this, true);
} catch (Exception e) {
Log.e("LayoutInflationError", e.getMessage());
}
List<ImageButton> buttons = new ArrayList<>();
int index = 0;
while (getChildAt(index) != null) {
ImageButton button = (ImageButton) getChildAt(index);
buttons.add(button);
index++;
}
Log.i("Buttons|=>", "" + buttons.size()); // gives Buttons|=>: 12
// attempting to add rows
for (int i = 0; i < buttons.size(); i++) {
TableRow row = new TableRow(context);
TableRow.LayoutParams lp = new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT);
row.setLayoutParams(lp);
dialpad = (ImageButton) buttons.get(i); // get an image button
row.addView(dialpad, i % 3); // add ImageButton to this row
if ((i + 1) % 3 == 0) {
addView(row, i);// add the row to the TableLayout after every 3rd entry
}
}
}
In this case, program crashes with the following errors
05-01 16:36:06.404 3269-3269 myPackage E/AndroidRuntime: FATAL EXCEPTION: main
Process: myPackage, PID: 3269
java.lang.RuntimeException: Unable to start activity ComponentInfo{myPackage/myPackage}: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:4915)
at android.view.ViewGroup.addView(ViewGroup.java:4746)
at android.view.ViewGroup.addView(ViewGroup.java:4686)
at myPackage.customView.DialPadView.init(DialPadView.java:52)
at myPackage.customView.DialPadView.(DialPadView.java:22)
at myPackage.MainActivity.onCreate(MainActivity.java:17)
at android.app.Activity.performCreate(Activity.java:6975)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Modified to
DialPadView.java
public class DialPadView extends TableLayout {
private List<Button> buttons;
public DialPadView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
buttons = new ArrayList<>();
loadButtonImages();
removeAllViews();
loadRowsForPortrate(context);
}
/**
* Give button all desired default functionality
*
* @param button Button to style
*/
private void styleButton(final Button button) {
button.setMaxHeight((int) getResources().getDimension(R.dimen.dialpadHeight));
button.setMaxWidth((int) getResources().getDimension(R.dimen.dialpadHeight));
button.setFocusable(true);
button.setClickable(true);
button.setFocusableInTouchMode(true);
button.setScaleX(0.12f);
button.setScaleY(0.12f);
}
/**
* Load the images to the various ImageButtons
*/
private void loadButtonImages() {
for (int i = 0; i < 12; i++) {
Button button = new Button(getContext());
styleButton(button);
buttons.add(button);
}
// give each button, its background image
if (!buttons.isEmpty()) {
buttons.get(0).setBackgroundResource(R.drawable.dialpad1);
buttons.get(1).setBackgroundResource(R.drawable.dialpad2);
buttons.get(2).setBackgroundResource(R.drawable.dialpad3);
buttons.get(3).setBackgroundResource(R.drawable.dialpad4);
buttons.get(4).setBackgroundResource(R.drawable.dialpad5);
buttons.get(5).setBackgroundResource(R.drawable.dialpad6);
buttons.get(6).setBackgroundResource(R.drawable.dialpad7);
buttons.get(7).setBackgroundResource(R.drawable.dialpad8);
buttons.get(8).setBackgroundResource(R.drawable.dialpad9);
buttons.get(9).setBackgroundResource(R.drawable.dialpadstar);
buttons.get(10).setBackgroundResource(R.drawable.dialpad0);
buttons.get(11).setBackgroundResource(R.drawable.dialpadpound);
}
}
// load rows for portrate view
private void loadRowsForPortrate(Context context) {
// Define row parameters
TableRow.LayoutParams params = new TableRow.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT);
params.setMargins(2, 2, 2, 2);
List<TableRow> tableRows = new ArrayList<>();
// create 4 rows and give them the row parameters above
for (int rows = 0; rows < 4; rows++) {
TableRow row = new TableRow(context);
row.setLayoutParams(params);
row.setGravity(Gravity.CENTER_VERTICAL);
tableRows.add(row);
}
// first row stuff
TableRow row1 = (TableRow) tableRows.get(0);
row1.addView(buttons.get(0), 0);
row1.addView(buttons.get(1), 1);
row1.addView(buttons.get(2), 2);
// Create second row and fill it with three imageButtons
TableRow row2 = (TableRow) tableRows.get(1);
row2.addView(buttons.get(3), 0);
row2.addView(buttons.get(4), 1);
row2.addView(buttons.get(5), 2);
// third row
TableRow row3 = (TableRow) tableRows.get(2);
row3.addView(buttons.get(6), 0);
row3.addView(buttons.get(7), 1);
row3.addView(buttons.get(8), 2);
// Fourth row
TableRow row4 = (TableRow) tableRows.get(3);
row3.addView(buttons.get(9), 0);
row3.addView(buttons.get(10), 1);
row3.addView(buttons.get(11), 2);
// add all rows to table
this.addView(row1);
this.addView(row2);
this.addView(row3);
this.addView(row4);
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DialPadView dialPadView = new DialPadView(this);
setContentView(dialPadView);
}
}
But i get just one image on the screen (number 1)
SOLVED Made the merge with TableRows then
<TableRow>
<ImageButton
android:id="@+id/oneButton"
style="@style/dialpadStyle"
android:background="@drawable/dialpad1"
android:contentDescription="@string/dialpad_1" />
<ImageButton
android:id="@+id/twoButton"
style="@style/dialpadStyle"
android:background="@drawable/dialpad2"
android:contentDescription="@string/dialpad_2" />
<ImageButton
android:id="@+id/threeButton"
style="@style/dialpadStyle"
android:background="@drawable/dialpad3"
android:contentDescription="@string/dialpad_3" />
</TableRow>
<TableRow>
//.... others
// in `DialPadView.java`
private void init(final Context context) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
try {
inflater.inflate(R.layout.dialpad_view, this, true);
} catch (Exception e) {
Log.e("InflatorError", e.getMessage());
}
int index = 0;
while (getChildAt(index) != null) {
TableRow row = (TableRow) getChildAt(index);
index++;
}
}


TableLayoutis meant to be used withTableRows, but your customViewlayout doesn't have any, so it looks like it's just adding the firstImageButtonthere tomatch_parentin both directions, and the rest get pushed out the bottom (sinceTableLayoutitself is a verticalLinearLayout).TableLayoutyou need to addTableRows. See stackoverflow.com/a/18207894/4168607 for reference.Views can have only one parent. All of yourImageButtons have already been added to your customTableLayout, so you get that Exception when you try to add them to aTableRow, too. You could conceivably remove each of them from theTableLayoutbefore adding them to theirTableRow, but it would be arguably simpler to just put them in<TableRow>s in the layout to begin with. You can define separate layouts for portrait and landscape, so you'll get the appropriate arrangements for each.