بناء الحد الأدنى للقابل للحياة من النرد أندرويد تطبيق Android باستخدام بنيات Android الأساسية.

Vector AndroidRelativeLayout ، ConstraintLayout ، LinearLayout ]ListView و ArrayAdapter ListView ومنطق Adapterمنذ فترة وجيزة ، دخلت في لعبة Mansions of Madness هذه اللوحة. تشبه اللعبة إلى حد ما الفكرة الكلاسيكية حيث يتجول اللاعبون حول منزل يحاولون حل بعض الغموض. إنها لعبة رائعة أوصي بها بشدة . على أي حال ، تستخدم اللعبة لفات الزهر لحل الإجراءات وأحداث اللعبة الأخرى. من الغريب أن على اللاعبين في بعض الأحيان أن يتضمن المزيد من الزهر أكثر من اللعبة تشمل (6)! قررت أن هذه كانت فرصة مثالية لبناء تطبيق Dice Roller مخصص. في هذا البرنامج التعليمي ، سأستخدم مكونات Android الأساسية لبناء قصر من الأسطوانة النرد الجنون.

تم تصميم تطبيق DICE هذا خصيصًا لعمليات Gansions of Madness Gameplay ، لذا أولاً سأوضح كيف تستخدم اللعبة النرد.
للحفاظ على الأمور بسيطة ، سيكون التطبيق قائمة رأسية قابلة للتمرير من الزهر. سيحتوي التطبيق على 3 أزرار لتشغيل وظائف "Roll Dice" و "ADD DICE" و "إزالة الزهر". سيكون لكل زهر "تعليق" و "تغيير" مقابل.

يتحول منصة Android دائمًا ، مما يجعل البرامج التعليمية مثل هذه عفا عليها الزمن مع مرور الوقت. للإشارة ، بيئة ديف:
بيئة Android


يمكن تغيير كل هذه الأسماء بعد إنشاء المشروع على الرغم من أنه يمكن أن يكون مرهقًا لمطاردة جميع مراجع الاسم إذا أصبح المشروع معقدًا.

Android لديه العديد من الإصدارات. مع كل إصدار ، يتغير النظام الأساسي. هذا يعني في الأساس أن هناك الكثير من أجهزة Android في العالم مع إصدارات مختلفة. يصبح هذا صداعًا لمطوري التطبيقات لأنه بناءً على المكتبات التي يستخدمها التطبيق ، قد يكون التطبيق غير متوافق مع أجهزة معينة. المقايضة هنا هي أن التطبيق باستخدام مكتبات Android الجديدة لا يمكن تشغيلها على الأجهزة القديمة. إذا كان يجب تشغيل التطبيق على الأجهزة القديمة ، فيجب على التطبيق استخدام بعض بنيات Android الأقدم.

القالب الأولي في الواقع لا يهم الكثير لهذا التطبيق. يكون رمز القالب مفيدًا في بعض الأحيان لأنه يسبق التخطيط والفئات الأولية مع بعض التعليمات البرمجية. بما أنني لن أستخدم أيًا من هذا ، فقد اخترت نشاطًا فارغًا.

يقوم مربع حوار إنشاء مشروع Android بتهيئة المشروع بالإنشاءات الأساسية:
Gradle هو إطار لتسهيل بناء مشاريع. على ويكيبيديا
يصف بيان Android التطبيق إلى Android. خصائص التطبيق مثل الأذونات ، والنشيطات. يمكن العثور على التفاصيل على صفحة وثائق Android.
للبدء ، استخدمت محرر SVG بسيط عبر الإنترنت يدعى Clker لاستخلاص الوجوه النردات كـ SVG.



بعد ذلك ، أقوم باستيرادها إلى مشروعي باستخدام استوديو Asset Asset Android Studio.


الأجهزة لها قرارات وأبعاد مختلفة. من الصعب التنبؤ بدقة الجهاز وبعده. يمكن أن يؤدي تحجيم JPEGs إلى رسومات ضبابية أو محببة. طريقة واحدة لمعالجة ذلك هي للرسومات هي استخدام SVG. هناك العديد من المقالات عبر الإنترنت تناقش SVG ولكن للرجوع إليها ، يرجى مراجعة مقالة Wikipedia SVG لمزيد من المعلومات.
يقدم Android طريقتين لوصف التخطيطات: تخطيطات البرمجيات و XML. وصف التخطيطات المعقدة برنامجياً أمر صعب للغاية ، لذا فإن معظم الناس يتجنبون ذلك بشكل عام. قد لا تكون كتابة XML كلها ممتعة ، لكن Android Studio يقدم عدة أدوات لتخفيف الألم. أداة معاينة ومحرر تخطيط Wsywig. ولكن حتى مع المحرر ، فإن الغوص في XML لا مفر منه تقريبًا.


يبدأنا قالب Android Studio الفارغ للنشاط بعنصر تخطيط ConstraintLayout . سنحتاج إلى مكونين في هذا التطبيق: منطقة النرد ومنطقة وحدة التحكم. ستكون منطقة الزهر قائمة نرد قابلة للتمرير وستكون وحدة التحكم هي الأزرار الثلاثة "إضافة" إزالة "" لفة ".

activity_main.xml <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/dice_list"
android:layout_height="0dp"
android:layout_width="match_parent"
app:layout_constraintBottom_toTopOf="@id/button_bar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
</ListView>
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/control_bar_height"
android:orientation="horizontal"
android:weightSum="3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/dice_list">
<Button
android:id="@+id/add_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_button_label"
android:onClick="addDice"/>
<Button
android:id="@+id/rem_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rem_button_label"
android:onClick="removeDice"/>
<Button
android:id="@+id/roll_dice_button"
android:layout_weight="1"
android:layout_gravity = "center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/roll_button_label"
android:onClick="rollDice"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
بالنسبة لأولئك غير المألوفين مع XML ، قد يبدو ما سبق مثل الرطوبة. إن شرح XML خارج نطاق هذا البرنامج التعليمي ، لكن Google و YouTube و Wikipedia هي موارد رائعة لأولئك الذين يبحثون عن مزيد من المعلومات. بالنسبة لهذا التصميم ، أستخدم ListView ، Button ، LinearLayout ، و ConstraintLayout . يمكن العثور على التفاصيل حول سماتها على صفحة وثائق Android.
كنت أبحث عن Layout يصف بسهولة مساحة أسفل الارتفاع الثابت (للأزرار) ومنطقة أعلى (للنرد) التي تملأ مساحة الشاشة المتاحة. يمنح LinearLayout عناصره الفرعية باستخدام الأوزان مما يجعلها غير مناسبة. لا توفر RelativeLayout القدرة على "ملء المساحة المتبقية" مما يجعلها غير مناسبة أيضًا.
بصريًا ، تبدو LinearLayout قريبة جدًا مما أحتاج إليه. ومع ذلك ، فإن LinearLayout مخصص لقائمة ثابتة للعناصر بدلاً من القوائم الديناميكية. بالنسبة لهذا التطبيق ، يمكن أن تحتوي قائمة DICE في أي مكان بين 0 و 25 DICE ، مما يجعل ListView هو مرشح أفضل.
القيمة 0dp خاصة ConstraintLayout التي تشير إلى أنه يجب أن تملأ المساحة المتبقية للوالد.
كما هو موضح أعلاه ، يتضمن كل صف النرد زرين وصورة زهر.

row.xml <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/row_height">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/row_height"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:padding="@dimen/row_padding">
<Button
android:id="@+id/dice_change_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/change_button_label">
</Button>
</FrameLayout>
<ImageView
android:id="@+id/dice_icon"
android:layout_centerInParent="true"
android:layout_width="@dimen/image_width"
android:layout_height="@dimen/image_height"
android:src="@drawable/blank_dice"/>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/row_height"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:padding="@dimen/row_padding">
<Button
android:id="@+id/dice_hold_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hold_button_label">
</Button>
</FrameLayout>
</RelativeLayout>
يجلس الزر في المركز العمودي للصف وبعض المسافة من كل حافة. شعرت أن التصميم سيكون أكثر نظافة إذا قام التصميم بفصل عنصر الزر الفعلي وموضعه في التصميم. لذلك بالنسبة لتطبيقي ، أستخدم FrameLayout لتحديد الموضع وتركز الزر في هذا التصميم.
تتيح لنا قيم السلسلة والأبعاد عدم كتابة سلاسل التكوين والأعداد الصحيحة مباشرة في التعليمات البرمجية. لتطبيقنا الصغير ، ربما ليست مشكلة كبيرة.
string.xml <resources>
<string name="app_name">DiceRoller</string>
<string name="hold_button_label">Hold</string>
<string name="change_button_label">Change</string>
<string name="add_button_label">ADD</string>
<string name="rem_button_label">REM</string>
<string name="roll_button_label">ROLL</string>
</resources>
dimens.xml <?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="row_height">72dp</dimen>
<dimen name="row_padding">16dp</dimen>
<dimen name="control_bar_height">72dp</dimen>
<dimen name="image_width">72dp</dimen>
<dimen name="image_height">72dp</dimen>
</resources>
ListView و ArrayAdapter في هذه المرحلة ، قمت بتهيئة مشروع Android الخاص بنا مع النشاط الرئيسي الفارغ وسخرت من بعض التخطيطات. بعد ذلك ، سأدخل في المنطق والرمز. للبدء ، أرغب في الدخول في بعض فصول Java الخاصة بنظام Android. ListView هي فئة تخطيط أساسية لتقديم القوائم المرئية. يفصل نظام Android Framework المكونات المرئية ( ListView ) ومكونات البيانات ( List<Dice> ) عن طريق استخدام نمط محول. في حالتنا ، فإن جميع المحول هو خريطة البيانات ( Dice ) لبعض التصميم البصري ( dice_row.xml ). في هذه الحالة ، يصف ملف XML تخطيط التصميم.
MainActivity.java public class MainActivity extends AppCompatActivity {
DiceAdapter diceAdapter;
List <Dice> diceList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//associating activity to layout
setContentView(R.layout.activity_main);
//Setup ListView and Adapter
ListView listView = findViewById(R.id.dice_list);
diceAdapter = new DiceAdapter(this, R.layout.dice_row, diceList);
listView.setAdapter(diceAdapter);
//Initialize Data
diceAdapter.add(new Dice());
}
public class DiceAdapter extends ArrayAdapter<Dice> {
public DiceAdapter(@NonNull Context context, int resource, List<Dice> list) {
super(context, resource, list);
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.dice_row, parent, false);
}
return convertView;
}
}
}
سيمثل التطبيق حالة النرد مع كائنات الزهر. يحتوي كائن Dice على خصائصين: قيمة الزهر [فارغة ، مكبرة ، نجمة] ، وما إذا كان النرد "محتجز". من الناحية الوظيفية ، يحتوي النرد على طريقة لفة من شأنها تحديد وجه النرد بشكل عشوائي. أخيرًا ، أضيف طريقة تغير قيمة الزهر إلى التالي في القائمة.
MainActivity.java ....
public static class Dice {
public enum Face {
BLANK,
MAGNIFY,
STAR
}
public static Random random = new Random();
boolean hold = false;
Face diceVal;
Dice() {
roll();
}
public void roll() {
int num = random.nextInt(4);
if(num == 0) { //25% magify
this.diceVal = Face.MAGNIFY;
} else {
//37.5% star, 37.5% blank
if(random.nextBoolean()) {
this.diceVal = Face.BLANK;
} else {
this.diceVal = Face.STAR;
}
}
}
public void toggleHold() {
hold = !hold;
}
public void nextValue() {
int index = diceVal.ordinal();
index = (index+1) % Face.values().length;
diceVal = Face.values()[index];
}
}
في هذه الخطوة ، ينقر زر الخريطة على المنطق. يقدم منصة Android طريقين للقيام بذلك. طريقة واحدة هي تحديد سمة من ملف التخطيط. آخر هو تعيين onClickListener برمجيا. في تطبيقنا ، استخدم نهج السمات لأزرار المستوى الأعلى الثلاثة وقم بتعيين المستمع برمجيًا لأزرار الصف.
addDice إذا كان عدد الزهر أقل من 25 ، يضيف كائن نرد جديد إلى قائمة النرد.
تصميم
activity_main.xml ....
<Button
android:id="@+id/add_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_button_label"
android:onClick="addDice"/>
....
منطق
MainActivity.java ....
public void addDice(View view) {
if(diceList.size()< MAX_DICE_COUNT) {
diceAdapter.add(new Dice());
}
}
....
removeDice إذا لم تكن قائمة الزهر فارغة ، فستزيل النرد الأخير من القائمة
تصميم
activity_main.xml ....
<Button
android:id="@+id/rem_dice_button"
android:layout_gravity = "center"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rem_button_label"
android:onClick="removeDice"/>
....
منطق
MainActivity.java ....
public void removeDice(View view) {
if(!diceList.isEmpty()) {
int lastIndex = diceList.size() - 1;
diceAdapter.remove(diceAdapter.getItem(lastIndex));
}
}
....
تقوم rollDice بإعادة قيمة كل زهر في قائمة لم يتم وضع علامة على الاحتفاظ بها.
تصميم
activity_main.xml ....
<Button
android:id="@+id/roll_dice_button"
android:layout_weight="1"
android:layout_gravity = "center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/roll_button_label"
android:onClick="rollDice"/>
....
منطق
MainActivity.java ....
public void rollDice(View view) {
//roll all dice
for(Dice dice : diceList) {
if(!dice.hold)
dice.roll();
}
//notify adapter to update view
diceAdapter.notifyDataSetChanged();
}
....
notifyDataSetChanged ؟ يغير زر Roll Dicevalue لكائن النرد المقابل. نظرًا لفصل نمط محول العرض/البيانات ، لا يتم إعادة تصميم صف Dice Row تلقائيًا ما لم يتم تشغيله. استدعاء notifyDataSetChanged يعيد الرؤية.
سيقوم النقر فوق زر Hold على تعيين علامة Hold's Dice.
MainActivity.java ....
Button holdButton = convertView.findViewById(R.id.dice_hold_button);
holdButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Dice dice = diceList.get(position);
dice.toggleHold();
}
});
....
سيؤدي النقر فوق زر Hold على تغيير قيمة DICE وتحديث واجهة.
MainActivity.java ....
Button changeButton = convertView.findViewById(R.id.dice_change_button);
changeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Dice dice = diceList.get(position);
dice.nextValue();
diceAdapter.notifyDataSetChanged();
}
});
....