English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
استخدمت في مشروع التخرج عدة عناصر مخصصة، وكنت دائمًا أرغب في تلخيص طرق إنشاء العناصر المخصصة، اليوم سأقوم بذلك. قبل ذلك، تعلمت من مدونة السيد جولين بعض المقالات حول إنشاء View المخصصة، وشعرت بالفائدة الكبيرة، وأشارت هذه المقالات إلى بعض المعلومات التي أستخدمها في هذا المقال.
باختصار، هناك ثلاث طرق لإنشاء عنصر مخصص، وهي: دمج العناصر، رسم العناصر، ووراثة العناصر. سأقوم بتقديم شرح لكل من هذه الطرق.
(أ) دمج العناصر
دمج العناصر، كما يُقال، هو دمج بعض العناصر الصغيرة لتشكيل عنصر جديد، وتلك العناصر الصغيرة غالبًا ما تكون عناصر نظام مدمجة. على سبيل المثال، يستخدم الكثير من التطبيقات شريط العنوان، وهو في الواقع يستخدم عنصر دمج، لذا سأشرح فيما يلي استخدام دمج العناصر من خلال إنشاء عنصر شريط عنوان مخصص بسيط.
1、إنشاء مشروع Android جديد، إنشاء ملف التخطيط المخصص لشريط العنوان title_bar.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="wrap_content" android:background="#0000ff" > <Button android:id="@+id/left_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_margin="5dp" android:background="@drawable/back1_64" /> <TextView android:id="@+id/title_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="هذا هو العنوان" android:textColor="#ffffff" android:textSize="20sp" /> </RelativeLayout>
يبدو أن واجهة تحكم الشريط العلوية هذه بسيطة نسبيًا، حيث يحتوي الجانب الأيسر على زر العودة، والخلفية هي صورة مسبقًا تحضيرها back1_64.png، والشريط العلوية في الوسط يحتوي على نص العنوان.
2、إنشاء فئة TitleView، تنسق من RelativeLayout:
public class TitleView extends RelativeLayout { // عنصر زر العودة private Button mLeftBtn; // عنوان TextView private TextView mTitleTv; public TitleView(Context context, AttributeSet attrs) { super(context, attrs); // تحميل التخطيط LayoutInflater.from(context).inflate(R.layout.title_bar, this); // الحصول على عنصر mLeftBtn = (Button) findViewById(R.id.left_btn); mTitleTv = (TextView) findViewById(R.id.title_tv); } // إضافة حادثة النقر المخصصة للزر العودة public void setLeftButtonListener(OnClickListener listener) { mLeftBtn.setOnClickListener(listener); } // طريقة تعيين العنوان public void setTitleText(String title) { mTitleTv.setText(title); } }
في TitleView، يتم تحميل التركيب المخصص لشريط العنوان المخصص، وإضافة طريقة الاستماع إلى الحادثة الخاصة بزر العودة، وتقديم طريقة لضبط نص العنوان.
3. في ملف activity_main.xml، استيراد شريط العنوان المخصص:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.example.test.TitleView android:id="@+id/title_bar" android:layout_width="match_parent" android:layout_height="wrap_content" > </com.example.test.TitleView> </LinearLayout>
4. في MainActivity، الحصول على شريط العنوان المخصص وتحديد زر العودة وإضافة حادثة النقر المخصصة:
private TitleView mTitleBar; mTitleBar = (TitleView) findViewById(R.id.title_bar); mTitleBar.setLeftButtonListener(new OnClickListener() { @Override public void onClick(View v) {}} Toast.makeText(MainActivity.this, "تم النقر على زر العودة", Toast.LENGTH_SHORT) .show(); finish(); } });
5. النتيجة بعد التشغيل كالتالي:
بهذه الطريقة، تم إنشاء شريط العنوان المخصص باستخدام طريقة التجميع، في الواقع، يمكن إنشاء تحكمات مخصصة أكثر تعقيدًا باستخدام مزيد من التجميع، مثل شريط البحث المخصص.
1) التحكم المرسوم
محتويات التحكم المرسوم هي رسمها بنفسها، في طريقة onDraw لـ View. لذا، سنعمل على إنشاء مقياس بسيط، حيث يزيد عدد النقرات بمجرد النقر عليه مرة واحدة ويظهر في الشاشة.
1、إنشاء فئة CounterView، التي تنشئ من View، وتحقق من واجهة OnClickListener:
public class CounterView extends View implements OnClickListener { // تعريف قلم الطلاء private Paint mPaint; // يستخدم لمعرفة أبعاد النص private Rect mBounds; // قيمة العداد، يزيد كل مرة نقوم بنقر هذا التحكم private int mCount; public CounterView(Context context, AttributeSet attrs) { super(context, attrs); // تعريف قلم الطلاء و Rect mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBounds = new Rect(); // تحديد انقراض هذا التحكم setOnClickListener(this); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(Color.BLUE); // رسم مستطيل ملون بالأزرق canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); mPaint.setColor(Color.YELLOW); mPaint.setTextSize(50); String text = String.valueOf(mCount); // الحصول على أبعاد النص mPaint.getTextBounds(text, 0, text.length(), mBounds); float textWidth = mBounds.width(); float textHeight = mBounds.height(); // رسم النص canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2 + textHeight / 2, mPaint); } @Override public void onClick(View v) {}} mCount++; // إعادة الرسم invalidate(); } }
2、في activity_main.xml، قم بإدخال هذا المخطط المخصص:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.example.test.CounterView android:id="@+id/counter_view" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center_horizontal|top" android:layout_margin="20dp" /> </LinearLayout>
3、运行效果如下:
(三)继承控件
就是继承已有的控件,创建新控件,保留继承的父控件的特性,并且还可以引入新特性。下面就以支持横向滑动删除列表项的自定义ListView的实现来介绍。
1、创建删除按钮布局delete_btn.xml,这个布局是在横向滑动列表项后显示的:
<?xml version="1.0" encoding="utf-8"?> <Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#FF0000" android:padding="5dp" android:text="删除" android:textColor="#FFFFFF" android:textSize="16sp" > </Button>
2、创建CustomListView类,继承自ListView,并实现了OnTouchListener和OnGestureListener接口:
public class CustomListView extends ListView implements OnTouchListener, OnGestureListener { // 手势动作探测器 private GestureDetector mGestureDetector; // 删除事件监听器 public interface OnDeleteListener { void onDelete(int index); } private OnDeleteListener mOnDeleteListener; // 删除按钮 private View mDeleteBtn; // 列表项布局 private ViewGroup mItemLayout; // 选择的列表项 private int mSelectedItem; // هل ظهر زر الحذف حاليًا؟ private boolean isDeleteShown; public CustomListView(Context context, AttributeSet attrs) { super(context, attrs); // إنشاء جهاز الاستماع إلى اللمس mGestureDetector = new GestureDetector(getContext(), this); // استماع إلى أحداث onTouch setOnTouchListener(this); } // إعداد أحداث الاستماع إلى الحذف public void setOnDeleteListener(OnDeleteListener listener) { mOnDeleteListener = listener; } // أحداث الاستماع إلى اللمس @Override public boolean onTouch(View v, MotionEvent event) { if (isDeleteShown) { hideDelete(); return false; } else { return mGestureDetector.onTouchEvent(event); } } @Override public boolean onDown(MotionEvent e) { if (!isDeleteShown) { mSelectedItem = pointToPosition((int) e.getX(), (int) e.getY()); } return false; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // إذا لم يكن زر الحذف ظاهرًا حاليًا، وكانت سرعة التمرير في اتجاه x أكبر من سرعة التمرير في اتجاه y if (!isDeleteShown && Math.abs(velocityX) > Math.abs(velocityY)) { mDeleteBtn = LayoutInflater.from(getContext()).inflate( R.layout.delete_btn, null); mDeleteBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {}} mItemLayout.removeView(mDeleteBtn); mDeleteBtn = null; isDeleteShown = false; mOnDeleteListener.onDelete(mSelectedItem); } }); mItemLayout = (ViewGroup) getChildAt(mSelectedItem - getFirstVisiblePosition()); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); params.addRule(RelativeLayout.CENTER_VERTICAL); mItemLayout.addView(mDeleteBtn, params); isDeleteShown = true; } return false; } // إخفاء زر الحذف public void hideDelete() { mItemLayout.removeView(mDeleteBtn); mDeleteBtn = null; isDeleteShown = false; } public boolean isDeleteShown() { return isDeleteShown; } /** * لم يتم استخدام بعض الطرق بعد في هذا المثال */ @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } @Override public void onLongPress(MotionEvent e) { } }
3- تعريف تصميم عنصر القائمة custom_listview_item.xml، والذي هو بسيط، يحتوي فقط على TextView واحدة:
<?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="match_parent" android:descendantFocusability="blocksDescendants" /> <TextView android:id="@+id/content_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_margin="30dp" android:gravity="center_vertical|left" /> </RelativeLayout>
4- تعريف فئة التكيف CustomListViewAdapter، تُرث من ArrayAdapter<String>:
public class CustomListViewAdapter extends ArrayAdapter<String> { public CustomListViewAdapter(Context context, int textViewResourceId, List<String> objects) { super(context, textViewResourceId, objects); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate( R.layout.custom_listview_item, null); } else { view = convertView; } TextView contentTv = (TextView) view.findViewById(R.id.content_tv); contentTv.setText(getItem(position)); return view; } }
5、在activity_main.xml中引入自定义的ListView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.example.test.CustomListView android:id="@+id/custom_lv" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
6、在MainActivity中对列表做初始化、设置列表项删除按钮点击事件等处理:
public class MainActivity extends Activity { // 自定义Lv private CustomListView mCustomLv; // 自定义适配器 private CustomListViewAdapter mAdapter; // 内容列表 private List<String> contentList = new ArrayList<String>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); initContentList(); mCustomLv = (CustomListView) findViewById(R.id.custom_lv); mCustomLv.setOnDeleteListener(new OnDeleteListener() { @Override public void onDelete(int index) { contentList.remove(index); mAdapter.notifyDataSetChanged(); } }); mAdapter = new CustomListViewAdapter(this, 0, contentList); mCustomLv.setAdapter(mAdapter); } // إعداد قائمة المحتوى private void initContentList() { for (int i = 0; i < 20; i++) { contentList.add("مقطع محتوى" + i); } } @Override public void onBackPressed() { if (mCustomLv.isDeleteShown()) { mCustomLv.hideDelete(); return; } super.onBackPressed(); } }
7- النتيجة بعد التنفيذ كالتالي:
هذا هو محتوى المقال كله، نأمل أن يكون قد ساعدكم في تعلم، ونأمل أيضًا أن تدعموا تعليمات الصياح.
بيان: محتوى هذا المقال تم جمعه من الإنترنت، وله حقوق الملكية الأصلية للمالك، تم إدراجه من قبل مستخدمي الإنترنت التبرع بتحميله، لا يمتلك هذا الموقع حقوق الملكية، لم يتم تعديل المحتوى بشكل يدوي، ولا يتحمل هذا الموقع أي مسؤولية قانونية متعلقة بذلك. إذا وجدت محتوى يشتبه في مخالفة حقوق النسخ، فلا تتردد في إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (عند إرسال البريد الإلكتروني، يرجى استبدال # بـ @) للإبلاغ، وقدم الدليل على الدليل، إذا تم التحقق من ذلك، سيتم حذف المحتوى المزعوم فورًا.