为 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
旨在与TableRow
s 一起使用,但您的自定义View
布局没有任何内容,所以看起来它只是将第一个ImageButton
添加到@ 987654351@ 在两个方向上,其余的被推出底部(因为TableLayout
本身是垂直的LinearLayout
)。
在TableLayout
中需要添加TableRows
。参考***.com/a/18207894/4168607。
View
s 只能有一个父级。您的所有ImageButton
s 都已添加到您的自定义TableLayout
,因此当您尝试将它们添加到TableRow
时也会收到该异常。可以想象,在将它们添加到 TableRow
之前,您可以从 TableLayout
中删除它们中的每一个,但可以说将它们放在布局中的 <TableRow>
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 锁屏的示例或教程?