为 android 创建自定义拨号盘视图

Posted

技术标签:

【中文标题】为 android 创建自定义拨号盘视图【英文标题】:Creating a custom Dialpad view for android 【发布时间】:2018-10-11 06:41:46 【问题描述】:

我一直在努力设计这样的拨号盘视图 在portrate和横向视图

要创建视图,以便在MainActivity.java 中,我有

DialPadView myDialPad = new DialPadView(context);
setContentView(myDialPad);

所有图像都已提供,当用户单击按钮时,它必须以某种方式进行更改。

我有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>

我已将res/layout/dialpad_view.xml 中的布局组件指定为

<?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>

其中@string/dialpadStyle被指定为(在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>

这就是DialPadView.java 的样子

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
    

我的MainActivity.java 看起来像这样

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

这给了我

我猜我没有很好地实现我的DialPadView.java 类或者问题出在哪里。任何提示/帮助将不胜感激。

在 cmets 之后,我将 init 函数修改为如下所示

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
            
        
    

在这种情况下,程序崩溃并出现以下错误

05-01 16:36:06.404 3269-3269 myPackage E/AndroidRuntime: 致命异常: main 进程:myPackage,PID:3269 java.lang.RuntimeException:无法启动活动 ComponentInfomyPackage/myPackage:java.lang.IllegalStateException:指定的孩子已经有一个父母。您必须首先在孩子的父母上调用 removeView()。 在 android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817) 在 android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892) 在 android.app.ActivityThread.-wrap11(未知来源:0) 在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593) 在 android.os.Handler.dispatchMessage(Handler.java:105) 在 android.os.Looper.loop(Looper.java:164) 在 android.app.ActivityThread.main(ActivityThread.java:6541) 在 java.lang.reflect.Method.invoke(本机方法) 在 com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) 引起:java.lang.IllegalStateException:指定的孩子已经有一个父母。您必须首先在孩子的父母上调用 removeView()。 在 android.view.ViewGroup.addViewInner(ViewGroup.java:4915) 在 android.view.ViewGroup.addView(ViewGroup.java:4746) 在 android.view.ViewGroup.addView(ViewGroup.java:4686) 在 myPackage.customView.DialPadView.init(DialPadView.java:52) 在 myPackage.customView.DialPadView.(DialPadView.java:22) 在 myPackage.MainActivity.onCreate(MainActivity.java:17) 在 android.app.Activity.performCreate(Activity.java:6975) 在 android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213) 在 android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770) 在 android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892) 在 android.app.ActivityThread.-wrap11(未知来源:0) 在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593) 在 android.os.Handler.dispatchMessage(Handler.java:105) 在 android.os.Looper.loop(Looper.java:164) 在 android.app.ActivityThread.main(ActivityThread.java:6541) 在 java.lang.reflect.Method.invoke(本机方法) 在 com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

修改为

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

但我在屏幕上只看到一张图片(第 1 号)


已解决 然后与 TableRows 合并

<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++;
    

【问题讨论】:

TableLayout 旨在与TableRows 一起使用,但您的自定义View 布局没有任何内容,所以看起来它只是将第一个ImageButton 添加到@ 987654351@ 在两个方向上,其余的被推出底部(因为TableLayout 本身是垂直的LinearLayout)。 TableLayout 中需要添加TableRows。参考***.com/a/18207894/4168607。 Views 只能有一个父级。您的所有ImageButtons 都已添加到您的自定义TableLayout,因此当您尝试将它们添加到TableRow 时也会收到该异常。可以想象,在将它们添加到 TableRow 之前,您可以从 TableLayout 中删除它们中的每一个,但可以说将它们放在布局中的 &lt;TableRow&gt;s 中会更简单。您可以为纵向和横向定义单独的布局,以便为每个布局获得适当的安排。 考虑发布解决方案作为答案。 PadLayout 是一个自定义 ViewGroup,用于简化在 Android 中实现 Dialpad。 【参考方案1】:

这就是我解决问题的方法。它应该适用于肖像和风景。

特此介绍portrate模式和相关sn-p。横屏模式只需重新排列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_
    android:layout_
    android:layout_weight="1"
    android:gravity="center">

    <EditText
        android:id="@+id/dialedNumbers"
        android:layout_
        android:layout_
        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_
    android:layout_
    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_
    android:layout_
    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_
    android:layout_
    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_
    android:layout_
    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) 

最终产品。 圈出的区域添加在activity_main.xml

【讨论】:

以上是关于为 android 创建自定义拨号盘视图的主要内容,如果未能解决你的问题,请参考以下文章

Android 自定义拨号器应用,连接未激活

Android:如何根据大小创建自定义布局

在哪里可以找到有关创建自定义 android 锁屏的示例或教程?

如何在android中为自定义视图创建setter和getter

如何为android中的所有视图使用相同的自定义字体?

需要帮助在Android中使用多个按钮自定义视图