1

I have been struggling to design a dialpad view like this portrate view
in portrate and

landscape view
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);
    }
}

This gives me myDialPad

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)
enter image description here


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++;
    }
}
5
  • 1
    TableLayout is meant to be used with TableRows, but your custom View layout doesn't have any, so it looks like it's just adding the first ImageButton there to match_parent in both directions, and the rest get pushed out the bottom (since TableLayout itself is a vertical LinearLayout). Commented May 1, 2018 at 7:41
  • In TableLayout you need to add TableRows. See stackoverflow.com/a/18207894/4168607 for reference. Commented May 1, 2018 at 7:42
  • Views can have only one parent. All of your ImageButtons have already been added to your custom TableLayout, so you get that Exception when you try to add them to a TableRow, too. You could conceivably remove each of them from the TableLayout before adding them to their TableRow, 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. Commented May 1, 2018 at 20:41
  • 1
    Consider posting the solution as an answer. Commented Jan 13, 2019 at 18:17
  • PadLayout is a custom ViewGroup to simplify implementing Dialpad in Android. Commented Mar 11, 2020 at 19:13

1 Answer 1

0

This is how i solved the problem. It was supposed to work for both portrate and landscape.

I hereby present the portrate mode and relevant snippet. The landscape mode is just a matter of re-arranging dialpad_portrate.xml

// dialpad_portrate.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">

<TableRow
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:gravity="center">

    <EditText
        android:id="@+id/dialedNumbers"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="0.7"
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:hint="@string/dialedNumberHint"
        android:inputType="text"
        android:textSize="20sp" />

    <ImageButton
        android:id="@+id/deleteButton"
        style="@style/dialpadStyle"
        android:layout_weight="0.2"
        android:contentDescription="@string/deleteNumbers"
        android:longClickable="true"
        android:src="@drawable/ic_action_ic_delete_pink" />

    <ImageButton
        android:id="@+id/callButton"
        style="@style/dialpadStyle"
        android:layout_weight="0.1"
        android:contentDescription="@string/callDialedNumbers"
        android:src="@drawable/ic_call_pink" />
</TableRow>

<TableRow
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:gravity="center">

    <ImageButton
        android:id="@+id/oneButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/one"
        android:src="@drawable/ic_dialpad_1_blue" />

    <ImageButton
        android:id="@+id/twoButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/two"
        android:src="@drawable/ic_dialpad_2_blue" />

    <ImageButton
        android:id="@+id/threeButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/three"
        android:src="@drawable/ic_dialpad_3_blue" />
</TableRow>

<TableRow
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:gravity="center">

    <ImageButton
        android:id="@+id/fourButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/four"
        android:src="@drawable/ic_dialpad_4_blue" />

    <ImageButton
        android:id="@+id/fiveButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/five"
        android:src="@drawable/ic_dialpad_5_blue" />

    <ImageButton
        android:id="@+id/sixButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/six"
        android:src="@drawable/ic_dialpad_6_blue" />
</TableRow>

<TableRow
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:gravity="center">

    <ImageButton
        android:id="@+id/sevenButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/seven"
        android:src="@drawable/ic_dialpad_7_blue" />

    <ImageButton
        android:id="@+id/eightButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/eight"
        android:src="@drawable/ic_dialpad_8_blue" />

    <ImageButton
        android:id="@+id/nineButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/nine"
        android:src="@drawable/ic_dialpad_9_blue" />
</TableRow>

<TableRow
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:gravity="center">

    <ImageButton
        android:id="@+id/starButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/star"
        android:src="@drawable/ic_dialpad_star_blue" />

    <ImageButton
        android:id="@+id/zeroButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/zero"
        android:src="@drawable/ic_dialpad_0_blue" />

    <ImageButton
        android:id="@+id/poundButton"
        style="@style/dialpadStyle"
        android:contentDescription="@string/pound"
        android:src="@drawable/ic_dialpad_pound_blue" />
</TableRow>

</merge>

//DialPadView.java

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.SoundPool;
import android.media.ToneGenerator;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TableLayout;
import android.widget.TableRow;

import java.util.ArrayList;
import java.util.List;

import mypackage.R;
import mypackage.Constants;

public class DialPadView extends TableLayout {
private static final int CALL_PERMISSION_CODE = 1000;
private final String TAG = getClass().getSimpleName().toUpperCase();
private LayoutInflater inflater;
private List<Integer> sounds;
private SoundPool soundPool;
private boolean soundsReady = false;
private List<ImageButton> buttons;
private boolean canReadSounds;
private EditText dialer;
String fileLocation;

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);
}

/**
 * Init function that initializes everything and changes the display based on the orientation of
 * the screen
 * @param context The view's context
 * @param attributeSet Attributesets (null in my case)
 */
private void init(final Context context, @Nullable final AttributeSet attributeSet) {
    PreferenceManager.setDefaultValues(context, R.xml.app_preferences, false);

    canReadSounds =
        Constants.getBooleanFromPreference(Constants.READ_DEVICE_STORAGE, getContext());

    fileLocation = getFileLocation(); // location of sound file

    setBackgroundColor(getResources().getColor(R.color.dialpadBackground));

    inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    buttons = new ArrayList<>();

 // check device orientation and present appropriate view
    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        makeLandscape();
    } else {
        makePortrate();
    }

    soundPool = new SoundPool(12, AudioManager.STREAM_MUSIC, 0);
    soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
        @Override
        public void onLoadComplete(final SoundPool soundPool, final int sampleId,
                                   final int status) {
            soundsReady = true;
        }
    });

    sounds = new ArrayList<>();
    if (canReadSounds) { // permission to read from user storage
        loadSounds();
    }

    doUserInteractions(); // when a user clicks on a number, a sound is played and the color changes. 
}


/**
 * Inflate rows for portrate display
 */
private void makePortrate() {
    try {
        inflater.inflate(R.layout.dialpad_portrate, this, true);
    } catch (Exception e) {
        Log.d("LayoutInflationError", e.getMessage());
    }

    int index = 0;
    while (getChildAt(index) != null) {
        if (index == 0) {
            TableRow actions = (TableRow) getChildAt(index);
            dialer = (EditText) actions.getChildAt(0);
        } else {
            TableRow row = (TableRow) getChildAt(index);
            for (int i = 0; i < row.getChildCount(); i++) {
                ImageButton v = (ImageButton) row.getChildAt(i);
                buttons.add(v);
            }
        }
        index++;
    }
}
// OTHER METHODS
 /**
 * Make landscape view
 */
private void makeLandscape() {}
/**
 * When an image is pressed, it plays a sound and its image changes from blue to pink
 */
@SuppressLint("ClickableViewAccessibility")
private void doUserInteractions() {}
 /**
 * Changes the ImageButton's background, plays the corresponding sound and clears on focused
 * buttons
 * @param button Current button on focuss
 */
private void performActions(ImageButton button) {}

/**
 * Change the image if it is on focus
 * @param view ImageButton
 */
private void changeImage(final ImageButton view) {}

/**
 * Restore image button if not focussed
 * @param view ImageButton
 */
private void restoreImageBackground(final ImageButton view) {}

 /**
 * If the current button is not on pressed on entered from the keyboard, restore the image
 * background
 * @param button current image (pressed or entered from the keyboard)
 */
private void restoreOtherImageResources(ImageButton button) {}

/**
 * Load the pre-defined sounds
 **/
private void loadSounds() {}

/**
 * Play a sound based on the image pressed
 * @param view ImageButton pressed
 */
private void playSound(ImageButton view) {}

/**
 * Play dmtf tones if user device does not have external storage
 * @param view the button pressed
 */
private void playDMTF(ImageButton view) {}

Final product. The circled area is added in activity_main.xml See enter image description here

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.