<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>آموزش‌های پایه - اندروید استودیو</title>
	<atom:link href="https://android-studio.ir/category/basic/feed/" rel="self" type="application/rss+xml" />
	<link>http://android-studio.ir/category/basic/</link>
	<description>آموزش برنامه نویسی اندروید</description>
	<lastBuildDate>Thu, 13 Apr 2023 12:29:21 +0000</lastBuildDate>
	<language>fa-IR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>کار با امولاتور و ساخت دیوایس مجازی (AVD)</title>
		<link>https://android-studio.ir/android-emulator-avd/</link>
					<comments>https://android-studio.ir/android-emulator-avd/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Tue, 12 Jul 2022 06:14:38 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<category><![CDATA[دانلود فایل‌ها و ابزار]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=202645</guid>

					<description><![CDATA[<p>تست و دیباگ پروژه یکی از مهم‌ترین فرایندهای توسعه‌ی اپلیکیشن اندرویدی به شمار می‌رود. در این فرایند پروژه روی یک دستگاه حقیقی یا مجازی اندرویدی اجرا می‌شود تا توسعه‌دهنده بتواند کارکرد کدهای خود را بررسی کرده و نواقص احتمالی را برطرف کند. در این آموزش قصد دارم نحوه‌ی کار با شبیه ساز (امولاتور) اندروید استودیو [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/android-emulator-avd/">کار با امولاتور و ساخت دیوایس مجازی (AVD)</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/android_emulator_avd_tutorial.png" alt="آموزش نصب امولاتور (شبیه ساز) اندروید استودیو و AVD" /></p>
<p>تست و دیباگ پروژه یکی از مهم‌ترین فرایندهای توسعه‌ی اپلیکیشن اندرویدی به شمار می‌رود. در این فرایند پروژه روی یک دستگاه حقیقی یا مجازی اندرویدی اجرا می‌شود تا توسعه‌دهنده بتواند کارکرد کدهای خود را بررسی کرده و نواقص احتمالی را برطرف کند.<br />
در این آموزش قصد دارم نحوه‌ی کار با شبیه ساز (امولاتور) اندروید استودیو و همچنین اضافه کردن و اجرای دیوایس‌ها از طریق AVD را بررسی کنم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#what-is-android-emulator">شبیه ساز یا Emulator چیست؟</a></li>
<li><a href="#avd-vs-other-android-emulators">چرا AVD؟</a></li>
<li><a href="#install-android-studio-emulator-avd">نصب امولاتور اندروید استودیو</a></li>
<li><a href="#download-avd-system-images">دانلود System Image ها</a></li>
</ul>
</div>
<h2 id="what-is-android-emulator">شبیه ساز یا Emulator چیست؟</h2>
<p>به نام خدا. در زمان نگارش این مبحث آموزشی، اندروید ۱۲ به عنوان آخرین نسخه‌ی پایدار این سیستم‌عامل منتشر شده و حدود ۱۲% از کل دیوایس‌های اندرویدی را پوشش می‌دهد. این یعنی هنوز درصد قبل توجهی از موبایل‌ها، تبلت‌ها و دستگاه‌های اندرویدی دیگر از نسخه‌های پایین‌تر استفاده می‌کنند.<br />
بنابراین شما به عنوان یک برنامه نویس و توسعه دهنده‌ی اندروید لازم است پروژه‌ی اندرویدی خود را روی چندین دستگاه اندرویدی با نسخه‌های متفاوت تست کنید تا مطمئن شوید اپلیکیشن روی اکثر دستگاه‌هایی که در اختیار مردم قرار دارد عملکرد صحیحی خواهد داشت.<br />
به عبارت دیگر شما باید چندین گوشی و تبلت خریداری کنید تا بتوانید پروژه را روی تک تک آنها بررسی کنید که علاوه بر افزایش هزینه، چالش‌های خاص خود را به همراه دارد.<br />
اینجاست که امولاتور یا شبیه سازها وارد میدان شده و کار را بسیار ساده‌تر می‌کنند. با استفاده از شبیه ساز می‌توان هر تعداد از دیوایس‌های مجازی اندرویدی را روی کامپیوتر نصب و اجرا کرد. یعنی دیگر خبری از دیوایس فیزیکی و مشکلات اتصال آن به اندروید استودیو نیست.<br />
با استفاده از امولاتور به راحتی می‌توانید چندین دیوایس مجازی با نسخه‌ی سیستم عامل و اندازه‌ی صفحه نمایش مختلف ایجاد و پروژه‌ی خود را روی آنها تست و دیباگ کنید.<br />
شبیه سازهای مختلفی برای اندروید ساخته شده که روی سیستم‌عامل‌های محبوب مانند ویندوز، لینوکس و مک قابل استفاده هستند. درحال حاضر <a href="https://android-studio.ir/install-genymotion/">Genymotion</a>، <a href="https://www.bluestacks.com/">BlueStacks</a> و <a href="https://www.memuplay.com/">MEMu</a> جزء گزینه‌های مطرح به شمار می‌روند.</p>
<h2 id="avd-vs-other-android-emulators">چرا AVD؟</h2>
<p>در اندروید استودیو از همان ابتدای کار یک امولاتور داخلی در اختیار توسعه‌دهندگان قرار گرفته بود که امکان مدیریت دیوایس‌ها با استفاده از ابزار AVD فراهم شده است.<br />
بااینحال از سال ۱۳۹۵ تا اوایل ۱۴۰۱ با توجه به مزایایی که Genymotion نسبت به AVD داشت (مانند سرعت بالاتر و حجم کم دیوایس‌ها) شخصاً ترجیح می‌دادم از جنی موشن استفاده کنم.<br />
اما در ماه‌های اخیر سیاست جنی موشن در خصوص ارائه‌ی نسخه‌ی دسکتاپ این شبیه ساز تغییراتی داشته که مهم‌ترین آنها عدم انتشار API برای نسخه‌های جدید اندروید و اعمال محدودیت ۱ ماهه برای نسخه‌ی رایگان (Personal) هستند. از این به بعد تمرکز تیم Genymotion بر ارائه‌ی خدمات بر بستر Cloud است که هزینه‌های آن برای برنامه نویسان ایرانی بسیار بالاست.<br />
در این مدت مشکلات AVD مخصوصا مساله‌ی سرعت اجرا هم تا حدود زیادی رفع شد و با توجه به شرایط فعلی، استفاده از شبیه ساز داخلی اندروید استودیو منطقی‌ترین گزینه محسوب می‌شود. همین مساله باعث شد آموزش کار با امولاتور داخلی اندروید استودیو و AVD را تهیه کنم.</p>
<h2 id="install-android-studio-emulator-avd">نصب امولاتور اندروید استودیو</h2>
<p>امولاتور اندروید استودیو به صورت پیش‌فرض فعال نیست و باید از طریق SDK Manager نصب شود. همانطور که در آموزش نصب و راه اندازی اندروید استودیو گفته شد، امکان نصب آفلاین گزینه‌های Emulator و HAXM وجود ندارد و صرفا باید به صورت آنلاین نصب انجام شود.<br />
همچنین هنگام نصب اندروید استودیو باید گزینه‌ی Android Virtual Device انتخاب شده باشد در غیر اینصورت امکان استفاده از دیوایس مجازی یا AVD وجود نخواهد داشت:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/install/3/choose_android_virtual_device.png" alt="انتخاب گزینه AVD هنگام نصب اندروید استودیو" /><figcaption>انتخاب گزینه AVD هنگام نصب اندروید استودیو</figcaption></figure>
<p>پس از اطمینان از برقراری ارتباط اینترنت و تغییر IP برای عبور از تحریم ایران، SDK Manager را باز می‌کنم. اگر برای تغییر IP در اندروید استودیو نیاز به راهنمایی دارید این آموزش را مطالعه کنید.<br />
گزینه Android Emulator را انتخاب و تایید می‌کنم تا دانلود و نصب آن شروع شود.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_android_emulator.png" alt="نصب Emulator از طریق SDK Manager اندروید استودیو" /><figcaption>نصب امولاتور از طریق SDK Manager اندروید استودیو</figcaption></figure>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_android_emulator_2.png" alt="نصب شبیه ساز اندروید استودیو" /></p>
<p>یک ابزار جانبی با نام Intel x86 Emulator Accelerator (Haxm) در لیست SDK وجود دارد که استفاده از آن بر روی پردازنده‌های اینتل باعث افزایش سرعت شبیه ساز می‌شود. بنابراین در صورتی که پردازنده شما Intel باشد حتما این ابزار را هم نصب کنید.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_intel_HAXM.png" alt="نصب HAXM در اندروید استودیو" /><figcaption>نصب HAXM (Intel x86 Emulator Accelerator)</figcaption></figure>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_intel_HAXM_2.png" alt="نصب HAXM در اندروید استودیو" /></p>
<p>در این مرحله حداکثر میزانی از RAM که به امولاتور باید اختصاص داده شود را تعیین می‌کنیم که من همان عدد پیش‌فرض یعنی ۲ گیگابایت را انتخاب می‌کنم. البته این عدد بعداً هم قابل تغییر است.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_intel_HAXM_3.png" alt="نصب Intel x86 Emulator Accelerator در اندروید استودیو" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_intel_HAXM_4.png" alt="نصب HAXM در اندروید استودیو" /></p>
<p>نصب HAXM هم به پایان رسید و حالا می‌توانیم از شبیه ساز و AVD استفاده کنیم.<br />
اگر به یاد داشته باشید قبلا در جلسه‌ی آموزش نصب اندروید استودیو توضیح دادیم که در SDK Platforms برای هر API چند System image مختلف وجود دارد که این سیستم ایمیج‌ها برای شبیه ساز داخلی اندروید استودیو استفاده می‌شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/avd_system_images_in_adk_manager.png" alt="System Image ها در SDK" /><figcaption>System Image ها در SDK</figcaption></figure>
<p>همانطور که در تصویر فوق مشاهده می‌کنید برای Android 12 به تعداد ۹ عدد System Image وجود دارد. توضیحات مربوط به هر سیستم ایمیج قبلا در قسمت توضیحات SDK در آموزش نصب اندروید استودیو قید شده بنابراین نیازی به تکرار نیست.<br />
امکان دانلود و نصب هرکدام از سیستم ایمیج‌ها از طریق SDK Manager وجود دارد اما برای جلوگیری از انتخاب گزینه اشتباه توصیه می‌کنم دانلود را در محیط AVD انجام دهید. در AVD سیستم ایمیج مناسب شما در تب Recommended قرار می‌گیرد و از دانلود سیستم ایمیج‌های غیر ضروری و حجم بالا جلوگیری می‌کند.<br />
گزینه Device Manager را از منوی تولبار یا نوار سمت راست اندروید استودیو انتخاب می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/android_studio_device_manager.png" alt="Device Manager اندروید استودیو" /><figcaption>Device Manager اندروید استودیو</figcaption></figure>
<p>پنجره Device Manager باز شد:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/device_manager_virtual_tab.png" alt="قسمت دیوایس‌های مجازی در Device Manager" /><figcaption>قسمت دیوایس‌های مجازی در Device Manager</figcaption></figure>
<p>در دیوایس منیجر دو تب Virtual (مجازی) و Physical (فیزیکی) وجود دارد. تب Virtual برای مدیریت دیوایس‌های مجازی استفاده می‌شود. در حال حاضر هیچ دیوایس مجازی در لیست وجود ندارد.<br />
روی دکمه Create device یا لینک Create Virtual device کلیک می‌کنم تا پنل ساخت دیوایس مجازی باز شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/add_device_to_avd.png" alt="اضافه کردن دیوایس مجازی در AVD" /><figcaption>اضافه کردن دیوایس مجازی در AVD</figcaption></figure>
<p>در سمت چپ، دیوایس‌ها دسته‌بندی شده که در حالت پیش‌فرض گزینه Phone و یکی از دیوایس‌های آن انتخاب شده است. دیوایس‌های مختلفی در لیست وجود دارد که بر اساس متغیرهایی مانند سایز صفحه نمایش و رزولوشن آن مشخص شده‌اند.<br />
من فعلا سایز خاصی مدنظرم نیست بنابراین همین دیوایس پیش فرض را انتخاب می‌کنم برای مرحله بعد.<br />
دیوایسی که انتخاب شده دارای برنامه Play Store است بنابراین سیستم ایمیجی دانلود خواهد شد که از پلی استور پشتیبانی می‌کند.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/select_system_image.png" alt="انتخاب و دانلود System Image در AVD" /><figcaption>انتخاب و دانلود System Image</figcaption></figure>
<p>در این مرحله باید نسخه‌ی system image مدنظر خود را انتخاب کنیم. برای مثال من قصد دارم دیوایس را با API 31 یعنی Android 12 بسازم. بنابراین همین API را باید انتخاب کنم اما در حال حاضر این API و تمامی API های دیگر یک گزینه دانلود مقابلشان قرار داده شده که نشان می‌دهد سیستم ایمیج هیچکدام دانلود و نصب نشده.<br />
روی گزینه Download اندروید ۱۲ کلیک می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/download_system_image.png" alt="دانلود System Image" /><figcaption>شروع دانلود System Image</figcaption></figure>
<p>دانلود system image آغاز شد. خبر خوب اینکه مانند سایر ابزار SDK و برخلاف Emulator و HAXM فایل‌های مربوط به system image را می‌توان در خارج از محیط اندروید استودیو دانلود و به صورت آفلاین به SDK اضافه کرد.<br />
با توجه به حجم بالای سیستم ایمیج‌ها (حدود ۱٫۵ گیگابایت) و لزوم استفاده از ابزار تغییر IP احتمال شکست دانلود بالاست بنابراین توصیه می‌کنم لینک فایل سیستم ایمیج را از قسمت بالا کپی کرده و با استفاده از یک برنامه مدیریت دانلود، دریافت کنید.<br />
هرچند بازهم برای دانلود فایل نیاز به استفاده از ابزار تغییر IP وجود دارد اما در صورت استفاده از برنامه‌های مدیریت دانلود، چنانچه فرایند دانلود در هر نقطه قطع شود، می‌توان مجدد دانلود را از همان نقطه ادامه داد در حالی که در اندروید استودیو دانلود از ابتدا آغاز خواهد شد.<br />
اگر همچنان قصد دارید دانلود به صورت خودکار و در محیط اندروید استودیو انجام شود فقط کافیست صبر کنید تا دانلود فایل به اتمام برسد.<br />
اما من ترجیح می‌دهم فایل را جداگانه دانلود و سپس به محل نصب SDK منتقل کنم. بنابراین لینک فایل را کپی و فرایند دانلود را متوقف می‌کنم.<br />
پوشه SDK را باز می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/add_system-images_folder_to_sdk.png" alt="اضافه کردن System Image به SDK به صورت آفلاین و دستی" /><figcaption>اضافه کردن System Image به SDK به صورت آفلاین و دستی</figcaption></figure>
<p>مشاهده می‌کنید یک پوشه با نام system-images در SDK من وجود دارد. علت اینکه این پوشه قبلا ساخته شده این است که من در قسمت قبل یک سیستم ایمیج را برای دانلود انتخاب کردم و فرایند دانلود آغاز شد.<br />
به محض شروع دانلود، پوشه‌های لازم برای قرارگیری سیستم ایمیج‌ها ایجاد می‌شود حتی اگر دانلود متوقف شود.<br />
بنابراین اگر فایل سیستم ایمیج را قبلا دانلود کرده‌اید یا لینک آن را در اختیار دارید و نیازی به انجام مراحل قبل برای دریافت لینک دانلود ندارید، این پوشه هم خودکار ساخته نشده و خودتان باید آنرا با همین نام ایجاد کنید.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> لینک دانلود سیستم ایمیج‌های پرکاربرد در انتهای همین صفحه قرار می‌گیرد.
</div>
<p>وارد پوشه system-images می‌شوم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/add_system_image_folder_to_sdk.png" alt="اضافه کردن سیستم ایمیج در SDK اندروید استودیو" /></p>
<p>یک پوشه با نام android-31 وجود دارد که مربوط به Android 12 است که در AVD برای دانلود انتخاب کرده بودم. داخل این پوشه به ترتیب به این شکل ساخته شده:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/add_google_apis_playstore_folder.png" alt="پوشه google_apis_playstore" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/add_x86_64_folder.png" alt="پوشه x86_64" /></p>
<p>حالا کافیست پوشه x86_64 را حذف کرده و پوشه‌ای با همین نام که در فایل زیپ سیستم ایمیج قرار دارد را جایگزین کنم. نام فایلی که من دانلود کردم x86_64-31_r09.zip است. در نامگذاری فایل سیستم ایمیج از جزئیات آن استفاده شده است.<br />
X86_64 یعنی دیوایسی که با این سیستم ایمیج ساخته شود از اپلیکیشن‌های ۶۴ بیتی هم پشتیبانی می‌کند. عدد ۳۱ مربوط به نسخه API است و r09 یعنی فایل سیستم فعلی نهمین نسخه بروزرسانی شده از این سیستم ایمیج است.<br />
به این ترتیب می‌توان یک سیستم ایمیج را بدون نیاز به دانلود آن در محیط اندروید استودیو نصب کرد.<br />
در اینجا چون دانلود سیستم ایمیج اندروید ۱۲ برای لحظه‌ای آغاز شده بود، با اینکه آنرا متوقف کردم، تمامی پوشه‌های مربوط به آن به طور خودکار ساخته شد. اما اگر به هر دلیلی این فرایند انجام نشود، این پوشه‌ها باید به صورت دستی ایجاد شود.<br />
برای مثال این سیستم ایمیج مربوط به API 31 بود بنابراین نام پوشه اصلی آن android-31 است. دیوایس Pixel 2 که در AVD انتخاب کردیم از نوعی بود که از Play Store پشتیبانی می‌کرد بنابراین پوشه بعدی با نام google_apis_playstore ساخته شده. اگر دیوایس از نوعی باشد که پلی استور را پشتیبانی نمی‌کند پوشه را با نام google_apis می‌سازیم.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> برای هر دیوایس مثل Pixel 2 یک سیستم ایمیج جداگانه وجود ندارد. برای مثال برای API 31 فقط یک سیستم ایمیج وجود دارد که از پلی استور و از اپلیکیشن‌های x86_64 پشتیبانی می‌کند.<br />
به عبارت دیگر چنانچه در آینده چندین دیوایس دیگر با همین مشخصات بخواهیم اضافه کنیم باز هم از همین سیستم ایمیج استفاده می‌شود و نیاز به دانلود فایل دیگری نیست.
</div>
<p>پوشه x86_64 که از فایل زیپ دانلود شده خروجی گرفتم را جایگزین این پوشه کرده و به پنجره ساخت دیوایس برمی‌گردم. با توجه به اینکه سیستم ایمیج به صورت آفلاین نصب شد یکبار با استفاده از دکمه Refresh لیست سیستم ایمیج‌ها را بروز می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/system_image_added.png" alt="سیستم ایمیج اندروید استودیو" /><figcaption>سیستم ایمیج به AVD اضافه شد</figcaption></figure>
<p>مشاهده می‌کنید API 31 نصب و گزینه Download حذف شده است. همچنین با انتخاب این API دکمه Next فعال شده و می‌توان به مرحله بعد رفت.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/avd_configuration.png" alt="تنظیمات دیوایس مجازی در AVD" /><figcaption>تنظیمات دیوایس مجازی</figcaption></figure>
<p>در این مرحله جزئیات دیوایس مجازی را می‌توان تغییر داد. مانند نام دیوایس و وضعیت افقی یا عمودی بودن دیوایس هنگام اجرا. با انتخاب گزینه Show Advanced Settings سایر تنظیمات فعال می‌شود.<br />
با کلیک روی Finish دیوایس ساخته شده و به لیست دیوایس‌های مجازی من اضافه می‌شود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/avd_created.png" alt="ایجاد دیوایس مجازی در AVD" /></p>
<p>در قسمت Actions چهار گزینه برای هر دیوایس وجود دارد. گزینه اول برای استارت دیوایس است. از زمان استارت دیوایس تا بوت شدن اندروید حدود ۲۰ ثانیه زمان لازم است که البته به سخت‌افزار شما بستگی دارد.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/start_avd.png" alt="استارت دیوایس مجازی در AVD اندروید استودیو" /><figcaption>استارت دیوایس مجازی در AVD</figcaption></figure>
<p>حالا می‌توان پروژه را روی این دیوایس Run یا Debug کرد. در نوار بالای دیوایس چند گزینه وجود دارد که کاربرد هرکدام مشخص است. یک گزینه‌ی ۳ نقطه هم وجود دارد که تنظیمات دستگاه مانند موقعیت مکانی، میکروفون، دوربین و سنسورها را در اختیار برنامه نویس قرار می‌دهد:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/avd_settings.png" alt="پنل تنظیمات دیوایس مجازی AVD" /><figcaption>پنل تنظیمات دیوایس مجازی AVD</figcaption></figure>
<p>این آموزش هم به پایان رسید. موفق و پیروز باشید.</p>
<h2 id="download-avd-system-images">دانلود System Image ها</h2>
<p>توجه داشته باشید دانلود این فایل‌ها تنها در صورتی لازم است که قصد نصب آفلاین سیستم ایمیج‌ها را داشته باشید.</p>
<p><span class="notice">تاریخ بروزرسانی فایل‌ها : ۱۴۰۱/۰۴/۲۵</span></p>
<p>» <a href="http://dl.android-studio.ir/sdk/system_images/x86_64-31_r09.zip">دانلود System Image Android 12 Google Play x86_64 (API 31) با حجم ۱٫۳ گیگابایت</a> | <a style="color:#A3C21B" href="http://dl2.android-studio.ir/sdk/system_images/x86_64-31_r09.zip">لینک کمکی</a></p>
<p>» <a href="http://dl.android-studio.ir/sdk/system_images/x86-24_r19.zip">دانلود System Image Android 7 Google Play x86 (API 24) با حجم ۷۷۵ مگابایت</a> | <a style="color:#A3C21B" href="http://dl2.android-studio.ir/sdk/system_images/x86-24_r19.zip">لینک کمکی</a></p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۲۲<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/04_1_emulator_avd.zip" class="button green edd-submit">دانلود رایگان با حجم ۲ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/04_1_emulator_avd.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/android-emulator-avd/">کار با امولاتور و ساخت دیوایس مجازی (AVD)</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/android-emulator-avd/feed/</wfw:commentRss>
			<slash:comments>74</slash:comments>
		
		
			</item>
		<item>
		<title>خداحافظ findViewById؛ سلام View Binding</title>
		<link>https://android-studio.ir/android-view-binding/</link>
					<comments>https://android-studio.ir/android-view-binding/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Wed, 14 Jul 2021 19:35:20 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=197632</guid>

					<description><![CDATA[<p>فکر می‌کنم عنوان این مبحث تا حد زیادی هدف این جلسه را برای شما روشن کرده باشد. View Binding در اندروید جایگزینی شایسته برای findViewById است که کار معرفی یک view در اکتیویتی یا فرگمنت را برای ما انجام می‌داد. قبلا برای تعریف هر view لازم بود یکبار متد findViewById فراخوانی شود که علاوه بر [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/android-view-binding/">خداحافظ findViewById؛ سلام View Binding</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/android_view_binding.png" alt="آموزش View Binding در اندروید" /><br />
فکر می‌کنم عنوان این مبحث تا حد زیادی هدف این جلسه را برای شما روشن کرده باشد. View Binding در اندروید جایگزینی شایسته برای findViewById است که کار معرفی یک view در اکتیویتی یا فرگمنت را برای ما انجام می‌داد.<br />
قبلا برای تعریف هر view لازم بود یکبار متد findViewById فراخوانی شود که علاوه بر افزایش حجم کدها، وقت زیادی را از برنامه نویس اندروید می‌گرفت. در این جلسه به بررسی قابلیت View Binding می‌پردازیم که ما را از شر findViewById راحت کرده و باعث افزایش سرعت کار و همچنین کاهش حجم کدهای پروژه می‌گردد. البته مزایای دیگری هم دارد که در ادامه جلسه به آن می‌پردازیم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#what-is-view-binding">View Binding چیست؟</a></li>
<li><a href="#bind-ways">مقایسه View Binding در اندروید با سایر ابزارها و روش‌ها</a>
<ul>
<li><a href="#findViewById">findViewById</a></li>
<li><a href="#butterknife">کتابخانه ButterKnife</a></li>
<li><a href="#data-binding">Data Binding</a></li>
<li><a href="#view-binding">View Binding</a></li>
</ul>
</li>
<li><a href="#android-view-binding-project">ساخت پروژه View Binding و بررسی آن</a>
<ul>
<li><a href="#enable-view-binding-in-android-studio">فعالسازی View Binding در اندروید استودیو</a></li>
<li><a href="#view-binding-in-activity">پیاده سازی View Binding در اکتیویتی</a></li>
<li><a href="#view-binding-classes">دسترسی به کلاس‌های View Binding</a></li>
<li><a href="#view-binding-include-layouts">View Binding و layout های include شده</a></li>
<li><a href="#ignore-view-binding">غیر فعال کردن View Binding برای یک لایه (layout) خاص</a></li>
<li><a href="#view-binding-in-fragment">پیاده سازی View Binding روی فرگمنت</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="what-is-view-binding">View Binding چیست؟</h2>
<p>به نام خدا. View Binding یکی از امکانات زیر مجموعه‌ی <a href="https://developer.android.com/jetpack" target="_blank" rel="noopener">Jetpack</a> است که در کنفرانس IO گوگل در سال ۲۰۱۹ معرفی و در سال ۲۰۲۰ در Android Studio 3.6 و به عبارت دیگر Gradle 3.6 امکان فعالسازی و استفاده از آن مهیا شد. بنابراین استفاده از این قابلیت تنها از این نسخه و به بالا امکان پذیر است.<br />
اولین مزیت View Binding این است که برای فعالسازی روی اندروید استودیو نیازی به افزودن (import) یک کتابخانه اضافی به پروژه اندرویدی نیست و درون پلاگین Gradle اندروید استودیو تعبیه شده است. بنابراین صرفا لازم است در فایل گریدلِ پروژه آنرا فعال کنیم.<br />
همانطور که در ابتدای جلسه اشاره شد، View Binding جایگزینی برای تعریف view ها به شیوه سنتی آن یعنی استفاده از findViewById به شمار می‌رود.<br />
اگر در حالت عادی در یک لایه XML تعداد ۱۰ عدد view (مانند Button، TextView، EditText و&#8230;) داشته باشیم برای هرکدام و به صورت جداگانه باید با استفاده از findViewById آنها را در Activity یا Fragment تعریف کنیم. چیزی شبیه به اکتیویتی زیر:</p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.testapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private Button sendBtn;
    private TextView textView;
    private EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendBtn = findViewById(R.id.send_btn);
        textView = findViewById(R.id.txt_view);
        editText = findViewById(R.id.edit_text);

    }
}
</pre>
<p>علاوه بر نیاز به نوشتن و تکرار کدهای اضافی، ایرادات و باگ‌هایی این شیوه‌ی معرفی view ها دارد که با جایگزینی آن با View Binding این ایرادات مرتفع می‌گردد.<br />
با استفاده از View Binding نیازی به تعریف view ها توسط برنامه نویس نبوده و فراخوانی view ها به صورت مستقیم توسط id (شناسه) آنها انجام می‌شود!</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> اگر قبلا اصطلاح Data Binding را در جایی دیده یا شنیده‌اید آنرا با View Binding اشتباه نگیرید. البته تا قبل از معرفی View Binding از Data Binding هم برای انجام این کار استفاده می‌شد اما از آنجایی که قابلیت‌های Data Binding به حذف findViewById محدود نمی‌شود، تیم توسعه اندروید زیرمجموعه‌ای از آن را با نام View Binding معرفی کرد که به صورت تخصصی تنها یک وظیفه را برعهده داشته باشد.<br />
ضمن اینکه در مستندات اندروید هم توصیه شده چنانچه صرفا به قابلیت تعریف view ها در پروژه خود نیاز داشته باشیم بجای Data Binding از View Binding استفاده کنیم. بکارگیری View Binding نسبت به Data Binding ساده‌تر بوده و کارایی (Performance) بهتری نیز به همراه خواهد داشت.
</div>
<h2 id="bind-ways">مقایسه View Binding در اندروید با سایر ابزارها و روش‌ها</h2>
<p>برای تعریف view ها در اندروید چندین روش و کتابخانه وجود دارد که هرکدام مزایا و معایب مخصوص به خود را داشته و البته در نهایت به این نتیجه می‌رسیم که استفاده از View Binding در اندروید بهینه ترین روش فعلی خواهد بود.<br />
در ادامه برای درک بهتر برتری View Binding نسبت به رقبا، به بررسی هرکدام می‌پردازیم:</p>
<h3 id="findViewById">findViewById</h3>
<p>نیازی به معرفی نیست بنابراین به بررسی مزایا و معایب می‌پردازم:<br />
<b>عدم تاثیر در سرعت بیلد:</b> شاید تنها مزیت آن را بتوان عدم تاثیر بر سرعت بیلد پروژه دانست چرا که هنگام اجرای برنامه کار می‌کند. اما این مزیت در برابر معایب متعدد آن اصلا قابل توجیه نیست.<br />
<b>کدهای اضافی:</b> اولین امتیاز منفی این است که به ازاء هر view می‌بایست یک متغیر جداگانه ایجاد شود که طبیعتا حجم کدهای ما را افزایش خواهد داد.<br />
<b>Type safety نیست:</b> واژه type به معنی &#8220;نوع&#8221; و safety به معنی &#8220;ایمنی&#8221; است. Type safety نبودن findViewById به این معنی است که در هنگام تعریف کردن یک view امکان بروز اشتباه در تعیین نوع آن وجود دارد.<br />
برای مثال ممکن است view از جنس TextView باشد و ما اشتباها آن را در یک متغیر از جنس EditText تعریف کنیم.<br />
<b>Null safe نیست:</b> اگر یک view موجود در layout فقط در شرایطی خاص در دسترس باشد ممکن است با یک خطای NullPointerException مواجه شویم. زیرا findViewById وضعیت نال بودن یا نبودن view را در شرایط مختلف بررسی نمی‌کند.<br />
برای مثال حالتی را درنظر بگیرید که دو نسخه از activity_main.xml در پروژه داریم که یکی برای حالت عادی (صفحه عمودی یا portrait) و دیگری برای حالت افقی (landscape) استفاده می‌شود. چنانچه یک view در هردو layout مشترک نبوده و تنها در حالت portrait بکار رفته باشد هنگام قرار گرفتن دستگاه کاربر در حالت landscape می‌تواند سبب بروز خطای null شود.</p>
<h3 id="butterknife">کتابخانه ButterKnife</h3>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/butterknife_android_library.png" alt="کتابخانه ButterKnife" /></p>
<p>تا قبل از معرفی Data Binding توسط تیم اندروید، <a href="https://jakewharton.github.io/butterknife/" target="_blank" rel="noopener">کتابخانه ButterKnife</a> یکی از پرکاربردترین ابزار برای این منظور به شمار می‌رفت. البته کاربرد این کتابخانه محدود به bind (متصل) کردن view ها نمی‌شود و برای resource ها مانند رشته‌ها، رنگ‌ها و سایزها نیز کاربرد دارد. اما در این جلسه تنها قسمت مربوط به view ها مدنظر ماست.<br />
<b>کاهش حجم کدها:</b> استفاده از این کتابخانه حجم کدها را نسبت به findViewById کاهش داده و به عبارتی کدهای تمیز تری می‌نویسیم. در این روش، به وسیله Annotation ها (حاشیه نویسی) می‌توان view های مدنظر را تعریف کرد. سه view ای که در قسمت قبل توسط findViewById تعریف شده بود را اینبار با استفاده از ButterKnife تعریف می‌کنم.<br />
پس از اضافه کردن کتابخانه به پروژه، توسط annotation با نام <span dir="ltr">@BindView</span> هرکدام از view ها قابل تعریف است:</p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.testapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import butterknife.BindView;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.send_btn) Button sendBtn;
    @BindView(R.id.txt_view) TextView textView;
    @BindView(R.id.edit_text) EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
}
</pre>
<p><b>Type safety نیست:</b> مانند findViewById کتابخانه ButterKnife هم type safety نیست و id هر نوع view ای را می‌توان به هر نوع View در کلاس جاوا متصل کرد. بنابراین ممکن است در هنگام اجرا با ارور Exception برخورد کنیم.<br />
<b>کاهش سرعت بیلد:</b> از آنجایی که در حین کامپایل پروژه یک پردازشگر Annotation برای تولید (generate) کدها اجرا می‌شود در سرعت بیلد تاثیر منفی می‌گذارد. هرچند این کاهش سرعت بیلد چیزی نیست که ما را از استفاده یک کتابخانه منصرف کند زیرا به قول معروف مزایای آن بر معایبش می‌چربد. البته نه الان که View Binding را داریم!</p>
<h3 id="data-binding">Data Binding</h3>
<p>تفاوت اساسی Data Binding با دو مورد قبل در این است که پس از فعالسازی این قابلیت در پروژه اندرویدی، به ازاء هر layout یا سایر resource ها به صورت خودکار یک کلاس ایجاد (generate) می‌شود. در این کلاس‌ها تمامی عناصر به صورت خودکار تعریف و مقدار دهی شده و به سادگی می‌توانیم در <a href="http://android-studio.ir/activity/" target="_blank" rel="noopener">اکتیویتی</a> یا <a href="http://android-studio.ir/android-fragments/" target="_blank" rel="noopener">فرگمنت</a> به آنها دسترسی داشته باشیم.<br />
<b>کاهش حجم کدها:</b> فعالسازی و استفاده از Data Binding نسبت به دو گزینه قبل نیاز به نوشتن کد کمتری دارد در نتیجه کدهای تمیزتری خواهیم داشت.<br />
<b>کاهش سرعت بیلد:</b> در کلاس‌های ساخته شده توسط Data Binding از Annotation ها برای تعریف منابع استفاده می‌شود بنابراین مانند ButterKnife این مساله در کاهش سرعت build شدن پروژه تاثیرگذار خواهد بود.<br />
<b>Type safety و Null safe است:</b> هردو مورد در Data Binding صدق می‌کند و از این بابت جای نگرانی نداریم.<br />
در خصوص Data Binding بیشتر از این به بیان جزئیات نمی‌پردازم. </p>
<h3 id="view-binding">View Binding</h3>
<p>رسیدیم به هدف! همانطور که در ابتدای مبحث گفته شد View Binding به نوعی زیر مجموعه‌ی Data Binding محسوب می‌شود که کاربرد آن در حذف findViewById خلاصه شده و به همین جهت علاوه بر ساده تر شدن فرآیند، نسبت به Data Binding خروجی بهینه‌تری را در اختیار ما قرار می‌دهد.<br />
<b>کاهش حجم کدها:</b> با فعالسازی و استفاده از View Binding حجم کد مورد نیاز برای استفاده از view ها به حداقل ممکن می‌رسد و در بین این ۴ گزینه بالاترین امتیاز را به خود اختصاص داده است. تنها با نوشتن id هر view در اکتیویتی یا فرگمنت به view مربوطه دسترسی خواهیم داشت!<br />
<b>افزایش سرعت بیلد:</b> شیوه کار View Binding هم مانند Data Binding است و با فعالسازی آن به ازاء هر layout یک کلاس ایجاد شده و تمامی view های آن درون کلاس به صورت خودکار تعریف می‌شود. اما در اینجا خبری از پردازش annotation ها نیست و در نتیجه سرعت بیلد به نسبت Data Binding بالاتر خواهد بود.<br />
<b>Type safety و Null safe است:</b> این دو مورد را از پدر خودش یعنی Data Binding به ارث برده و با یکدیگر مشترک هستند. </p>
<p>حالا که گزینه‌های مختلف را بررسی کردیم و فهمیدیم بهترین گزینه در حال حاضر View Binding است در ادامه جلسه این قابلیت را در قالب یک پروژه بررسی و تمرین می‌کنیم.</p>
<h2 id="android-view-binding-project">ساخت پروژه View Binding و بررسی آن</h2>
<p>ابتدا طبق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام ViewBinding می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.<br />
در ابتدا و قبل از هرچیز لازم است قابلیت View Binding را در پروژه خود در اندروید استودیو فعال کنیم.</p>
<h3 id="enable-view-binding-in-android-studio">فعالسازی View Binding در اندروید استودیو</h3>
<p>همانطور که قبلا اشاره شد این قابلیت به صورت پیش فرض در گریدل نسخه ۳٫۶ به بالا تعبیه شده و برای فعالسازی آن نیازی به اضافه کردن کتابخانه به پروژه نیست.<br />
برای فعالسازی کافیست بلاک زیر را درون بلاک android در فایل build.gradle (project) اضافه و سپس پروژه را Sync کنم:</p>
<pre class="brush: java; title: ; notranslate">
buildFeatures {
    viewBinding = true
}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> چنانچه از اندروید استودیو ۳٫۶ و تا قبل از ۴٫۰ استفاده می‌کنید فعالسازی View Binding به صورت زیر انجام می‌شود:</p>
<pre class="brush: java; title: ; notranslate">
viewBinding {
    enabled = true
}
</pre>
<p>البته واضح است که استفاده از نسخه‌های قدیمی اندروید استودیو توصیه نمی‌شود.
</p></div>
<h3 id="view-binding-in-activity">پیاده سازی View Binding در اکتیویتی</h3>
<p>همانطو که قبلا گفته شد، با فعالسازی View Binding به ازاء هر layout موجود در پروژه یک کلاس جداگانه ایجاد می‌شود. نام هر کلاس از نام layout مربوط به آن گرفته می‌شود که البته به صورت camel case نوشته شده و پسوند Binding هم به انتهای آن اضافه می‌شود.<br />
برای مثال در این پروژه ما یک layout با نام activity_main.xml داریم. کلاسی که برای این layout ایجاد می‌شود ActivityMainBinding.java نام دارد. به عنوان یک مثال دیگر چنانچه در آینده یک layout با نام activity_map.xml به پروژه اضافه کنیم، کلاس ایجاد شده ActivityMapBinding.java نام خواهد داشت.<br />
در این کلاس به ازاء هر view (ویجت) ای که در activity_main.xml وجود داشته و یک id به آن اختصاص داده شده باشد، یک متغیر ایجاد می‌شود و ما در اکتیویتی به آن دسترسی خواهیم داشت.<br />
در layout پیش فرض پروژه یک View Group از جنس <a href="http://android-studio.ir/constraintlayout/" target="_blank" rel="noopener">ConstraintLayout</a> و یک TextView وجود دارد که البته برای TextView شناسه (id) به صورت پیش فرض تعریف نشده. بنابراین برای آنکه به این view دسترسی داشته باشم یک id به آن اختصاص می‌دهم:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;TextView
      android:id=&quot;@+id/txt_view&quot;
      android:layout_width=&quot;wrap_content&quot;
      android:layout_height=&quot;wrap_content&quot;
      android:text=&quot;Hello World!&quot;
      app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
      app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
      app:layout_constraintRight_toRightOf=&quot;parent&quot;
      app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

  &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>در مرحله بعد لازم است یک آبجکت (شیء) از کلاس ActivityMainBinding داخل اکتیویتی تعریف کنیم تا به view ها به صورت مستقیم دسترسی داشته باشیم.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/create_object_from_binding_class_in_activity.png" alt="ساخت آبجکت از کلاس View Binding" /><figcaption>ساخت آبجکت از کلاس View Binding</figcaption></figure>
<p>مشاهده می‌کنید کلاس ActivityMainBinding توسط اندروید استودیو شناسایی می‌شود. یعنی کلاس قبلا ساخته شده و قابل استفاده است.</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.viewbinding;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

import ir.android_studio.viewbinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mainBinding = ActivityMainBinding.inflate(getLayoutInflater());

        setContentView(R.layout.activity_main);


    }
}
</pre>
<p>نام دلخواه mainBinding را برای کلاس انتخاب کردم. سپس درون متد onCreate و قبل از setContentView توسط متد inflate آنرا مقداردهی کردم.<br />
حالا به View Group اصلی layout و تمام view هایی که قبلا به layout اضافه شده و یا بعد از این اضافه شود دسترسی خواهیم داشت. البته مجدد تاکید می‌کنم view هایی که دارای ویژگی id باشند. بجز عنصر اصلی یا ریشه‌ی layout که نیازی به id نداشته و در متغیری با نام root ذخیره شده و توسط getRoot در دسترس است. در اینجا عنصر ریشه ما یک ConstraintLayout است که قبلا در جلسه <a href="http://android-studio.ir/constraintlayout/" target="_blank" rel="noopener">آموزش کار با ConstraintLayout</a> با آن آشنا شدیم.<br />
ابتدا لازم است getRoot را به متد setContentView پاس بدهیم تا به اکتیویتی اعلام شود از طریق آبجکت Binding ای که ساخته‌ایم به layout دسترسی داشته باشد. getRoot را جایگزین R.layout.activity_main می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
setContentView(mainBinding.getRoot());
</pre>
<p>حالا برای تست View Binding با استفاده از متد setText یک متن را روی txt_view چاپ می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mainBinding = ActivityMainBinding.inflate(getLayoutInflater());

    setContentView(mainBinding.getRoot());

    mainBinding.txtView.setText(&quot;تست ViewBinding&quot;);

}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> نحوه نامگذاری id ها توسط View Binding به صورت camel case است. بنابراین همانطور که در کد فوق مشاهده می‌کنید txtView مربوط به txt_view موجود در layout است.
</div>
<p>پروژه را روی <a href="http://android-studio.ir/install-genymotion/" target="_blank" rel="noopener">امولاتور (شبیه ساز) اندرویدی</a> اجرا می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/uset_View_Binding_in_android_activity.png" alt="پیاده سازی View Binding در اکتیویتی" /><figcaption>پیاده سازی View Binding در اکتیویتی</figcaption></figure>
<p>طبق تصویر فوق هم layout نمایش داده شد و هم متنی که در setText قرار داده بودیم.<br />
برای تمرین و آشنایی بیشتر، یک Button به layout اضافه کرده و در اکتیویتی یک setOnClickListener برای آن تعریف می‌کنم. قبلا در <a href="http://android-studio.ir/event-handling/" target="_blank" rel="noopener">آموزش کار با رویدادها در اندروید</a> با این متد آشنا شده‌ایم. خط مربوط به setText را به درون رویداد مربوط به دکمه منتقل می‌کنم تا بعد از کلیک روی دکمه اجرا شود:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;Button
        android:id=&quot;@+id/set_btn&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;80dp&quot;
        android:text=&quot;جایگذاری متن&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/txt_view&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;Hello World!&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/set_btn&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.viewbinding;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

import ir.android_studio.viewbinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mainBinding = ActivityMainBinding.inflate(getLayoutInflater());

        setContentView(mainBinding.getRoot());

        mainBinding.setBtn.setOnClickListener(view -&gt; {

            mainBinding.txtView.setText(&quot;تست ViewBinding&quot;);

        });

    }
}
</pre>
<p>مجدد پروژه را اجرا کرده و روی دکمه جایگذاری متن کلیک می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/onClick_event_using_View_Binding.png" alt="دسترسی به Button توسط View Binding" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/onClick_event_using_View_Binding_2.png" alt="دسترسی به TextView توسط View Binding" /><figcaption></figcaption></figure>
<h3 id="view-binding-classes">دسترسی به کلاس‌های View Binding</h3>
<p>اگر مایل بودید به محتوای کلاس‌های ساخته شده توسط View Binding دسترسی داشته باشید، بعد از بیلد شدن پروژه (یعنی هنگام اجرای پروژه روی دیوایس یا گزینه Rebuild Project در تب Build و یا گرفتن خروجی APK از پروژه) در مسیر زیر فایل کلاس‌ها در دسترس هستند:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/View_Binding_classes_direcroty.png" alt="مسیر کلاس‌های View Binding" /><figcaption>مسیر کلاس‌های View Binding</figcaption></figure>
<p>برای نمایش محتوای دایرکتوری build لازم است نحوه نمایش پروژه در حالت Project و یا Project Files قرار گیرد. البته در پوشه‌ای که پروژه ذخیره شده هم می‌توان به فایل‌ها دسترسی داشت.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> در صورت عدم نمایش کلاس‌های مربوط به View Binding در قسمت نمایش ساختار پروژه در اندروید استودیو و البته بعد از بیلد شدن پروژه، راست کلیک کرده و Reload from Disk کنید تا محتوای پروژه بروز شود.
</div>
<p>محتوای فعلی کلاس ActivityMainBinding.java پروژه من به اینصورت است:</p>
<p><span class="filename">ActivityMainBinding.java</span></p>
<pre class="brush: java; title: ; notranslate">
// Generated by view binder compiler. Do not edit!
package ir.android_studio.viewbinding.databinding;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.viewbinding.ViewBinding;
import ir.android_studio.viewbinding.R;
import java.lang.NullPointerException;
import java.lang.Override;
import java.lang.String;

public final class ActivityMainBinding implements ViewBinding {
  @NonNull
  private final ConstraintLayout rootView;

  @NonNull
  public final Button setBtn;

  @NonNull
  public final TextView txtView;

  private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull Button setBtn,
      @NonNull TextView txtView) {
    this.rootView = rootView;
    this.setBtn = setBtn;
    this.txtView = txtView;
  }

  @Override
  @NonNull
  public ConstraintLayout getRoot() {
    return rootView;
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToParent) {
    View root = inflater.inflate(R.layout.activity_main, parent, false);
    if (attachToParent) {
      parent.addView(root);
    }
    return bind(root);
  }

  @NonNull
  public static ActivityMainBinding bind(@NonNull View rootView) {
    // The body of this method is generated in a way you would not otherwise write.
    // This is done to optimize the compiled bytecode for size and performance.
    int id;
    missingId: {
      id = R.id.set_btn;
      Button setBtn = rootView.findViewById(id);
      if (setBtn == null) {
        break missingId;
      }

      id = R.id.txt_view;
      TextView txtView = rootView.findViewById(id);
      if (txtView == null) {
        break missingId;
      }

      return new ActivityMainBinding((ConstraintLayout) rootView, setBtn, txtView);
    }
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException(&quot;Missing required view with ID: &quot;.concat(missingId));
  }
}
</pre>
<div class="alert alert-warning">
<span class="notice">تذکر:</span> همانطور که در کامنت ابتدای کلاس ذکر شده این کلاس توسط کامپایلر View Binding ساخته شده و نباید به هیچ عنوان به صورت دستی ویرایش شود.
</div>
<p>ملاحظه می‌کنید برای همه متغیرها انوتیشین <span dir="ltr">@NonNull</span> قید شده یعنی هیچکدام از آیتم‌ها تحت هیچ شرایطی نال نخواهند شد.<br />
اما فرض کنید در پروژه خود یک activity_main.xml دیگر هم داشته باشیم که مربوط به طراحی صفحه در حالت Landscape (افقی) باشد و در این حالت یکی از view ها را حذف کرده باشیم. یا بلعکس یک view در این حالت اضافه کرده باشیم که قرار نیست در layout اصلی یعنی حالت عمودی وجود داشته باشد. در اینصورت بجای <span dir="ltr">@NonNull</span> انوتیشین <span dir="ltr">@Nullable</span> جایگزین خواهد شد که نشان می‌دهد این view می‌تواند در شرایطی نال باشد. بنابراین وضعیت نال مدیریت شده و به اصطلاح Null safe هستند.<br />
به متد انتهای کلاس دقت کنید:</p>
<pre class="brush: java; title: ; notranslate">
@NonNull
public static ActivityMainBinding bind(@NonNull View rootView) {
  // The body of this method is generated in a way you would not otherwise write.
  // This is done to optimize the compiled bytecode for size and performance.
  int id;
  missingId: {
    id = R.id.set_btn;
    Button setBtn = rootView.findViewById(id);
    if (setBtn == null) {
      break missingId;
    }

    id = R.id.txt_view;
    TextView txtView = rootView.findViewById(id);
    if (txtView == null) {
      break missingId;
    }

    return new ActivityMainBinding((ConstraintLayout) rootView, setBtn, txtView);
  }
  String missingId = rootView.getResources().getResourceName(id);
  throw new NullPointerException(&quot;Missing required view with ID: &quot;.concat(missingId));
}
</pre>
<p>حدس می‌زنم انتظارش را نداشتید اینجا با findViewById مواجه شوید! اما واقعیت جز این نیست 🙂<br />
View Binding از متد findViewById برای اتصال view ها به اکتیویتی استفاده می‌کند و انجام این مرحله تکراری و زجر آور را از روی دوش برنامه نویس برمی‌دارد. علاوه بر آن، در خصوص type و null هم ما را ایمن نگه می‌دارد.</p>
<h3 id="view-binding-include-layouts">View Binding و layout های include شده</h3>
<p>اگر بخاطر داشته باشید در جلسه <a href="http://android-studio.ir/android-navigation-drawer" target="_blank" rel="noopener">آموزش کار با Navigation Drawer</a> با استفاده از تگ include یک layout را درون layout اصلی برنامه اضافه کردیم.<br />
دسترسی به view های layout ای که در layout یک اکتیویتی include شده امکان پذیر است. یک layout جدید با نام layout_bottom.xml به پروژه اضافه کرده و یک TextView با شناسه btm_txt داخل آن تعریف می‌کنم:</p>
<p><span class="filename">layout_bottom.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&gt;

    &lt;TextView
        android:id=&quot;@+id/btm_txt&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;متن پایینی&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;
    
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>سپس layout را درون activity_main.xml توسط تگ include اضافه می‌کنم. دقت داشته باشید برای دسترسی به layout ضمیمه شده و view های درون آن توسط View Binding حتما برای تگ include هم باید یک id تعریف شود. من شناسه include_layout را انتخاب کردم:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;Button
        android:id=&quot;@+id/set_btn&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;80dp&quot;
        android:text=&quot;جایگذاری متن&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/txt_view&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;252dp&quot;
        android:text=&quot;Hello World!&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/set_btn&quot; /&gt;

    &lt;include
        layout=&quot;@layout/layout_bottom&quot;
        android:id=&quot;@+id/include_layout&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/txt_view&quot;
        app:layout_constraintVertical_bias=&quot;0.706&quot;
        tools:layout_editor_absoluteX=&quot;-16dp&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>حالا برای دسترسی به btm_txt در layout_bottom به اینصورت عمل می‌کنیم:</p>
<pre class="brush: java; title: ; notranslate">
mainBinding.includeLayout.btmTxt
</pre>
<p>includeLayout همان شناسه include_layout است.<br />
برای مثال متد setText را برای این TextView استفاده می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
mainBinding.setBtn.setOnClickListener(view -&gt; {

    mainBinding.txtView.setText(&quot;تست ViewBinding&quot;);
    mainBinding.includeLayout.btmTxt.setText(&quot;تعویض متن پایینی&quot;);

});
</pre>
<p>برای layout_bottom.xml هم یک کلاس ایجاد شده که LayoutBottomBinding.java نام دارد اما به دلیل اینکه در اینجا layout را درون یک layout دیگر include کرده‌ایم که قبلا یک آبجکت از آن در اکتیویتی ساخته شده، بدون نیاز به ساخت آبجکت جدید از layout زیرمجموعه، می‌توان به آن دسترسی داشت.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/included_layout_View_Binding_class.png" alt="دسترسی به view یک لایه include شده در اکتیویتی" /><figcaption>دسترسی به view یک لایه include شده در اکتیویتی</figcaption></figure>
<p>با اجرای پروژه و کلیک روی دکمه جایگذاری متن، متن این TextView هم تغییر می‌کند:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/View_Binding_for_include_layouts.png" alt="دسترسی به view یک لایه include شده در اکتیویتی" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/View_Binding_for_include_layouts_2.png" alt="دسترسی به view یک لایه include شده در اکتیویتی" /></p>
<h3 id="ignore-view-binding">غیر فعال کردن View Binding برای یک لایه (layout) خاص</h3>
<p>ممکن است در پروژه خود برای یک یا چند layout نیازی به ساخت کلاس View Binding نداشته باشیم. با توجه به اینکه افزایش تعداد کلاس‌ها در نهایت موجب کاهش سرعت بیلد و همچنین افزایش حجم پروژه می‌شود لذا می‌طلبد برای لایه‌هایی که به صورت ایستا (static) هستند و کاری با view های داخل آن نداریم، قابلیت View Binding را غیر فعال کنیم.<br />
برای انجام این کار کافیست خط زیر به تگ layout ریشه آن اضافه شود:</p>
<pre class="brush: xml; title: ; notranslate">
tools:viewBindingIgnore=&quot;true&quot;
</pre>
<p>برای مثال من یک لایه با نام layout_top.xml در پروژه ایجاد کرده‌ام و با توجه به ایستا بودن محتوای آن قصد دارم View Binding را روی این فایل غیر فعال کنم:</p>
<p><span class="filename">layout_top.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    tools:viewBindingIgnore=&quot;true&quot;&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>واژه ignore به معنی &#8220;نادیده گرفتن&#8221; و &#8220;رد کردن&#8221; است بنابراین چنانچه برای این ویژگی مقدار true تعیین شود، قابلیت View Binding برای layout مدنظر نادیده گرفته خواهد شد.</p>
<h3 id="view-binding-in-fragment">پیاده سازی View Binding روی فرگمنت</h3>
<p>قبلا در مبحث <a href="http://android-studio.ir/android-fragments/" target="_blank" rel="noopener">فرگمنت‌ها در اندروید</a> با کاربرد Fragment آشنا شدیم. استفاده از View Binding در فرگمنت تفاوت زیادی با اکتیویتی ندارد.<br />
یک فرگمنت با نام TestFragment به پروژه اضافه می‌کنم. سپس در layout فرگمنت یعنی fragment_text.xml یک TextView با شناسه fragment_txt تعریف می‌کنم:</p>
<p><span class="filename">fragment_test.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;FrameLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.TestFragment&quot;&gt;

    &lt;!-- TODO: Update blank fragment layout --&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;متن پیش فرض فرگمنت&quot;
        android:id=&quot;@+id/fragment_txt&quot;/&gt;

&lt;/FrameLayout&gt;
</pre>
<p>سپس در کلاس فرگمنت مانند آنچه قبلا در اکتیویتی انجام شد یک آبجکت از کلاسی که View Binding برای layout فرگنت ایجاد کرده می‌سازم:</p>
<p><span class="filename">TextFragment.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.viewbinding;

import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import ir.android_studio.viewbinding.databinding.FragmentTestBinding;

public class TestFragment extends Fragment {

    private FragmentTestBinding frgBinding;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        //return inflater.inflate(R.layout.fragment_test, container, false);

        frgBinding = FragmentTestBinding.inflate(inflater, container, false);

        return frgBinding.getRoot();

    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();

        frgBinding = null;

    }
}
</pre>
<p>مطابق کد فوق ابتدا از کلاس FragmentTestBinding یک نمونه با نام frgBinding ساخته‌ام. سپس inflate اصلی فرگمنت را حذف (کامنت) کرده و inflate مربوط به View Binding را مانند اکتیویتی و البته اندکی تفاوت بجای آن تعریف کرده‌ام. در خط بعد هم متد getRoot برگردانده یا return شد.<br />
همچنین متد onDestroyView که مربوط به چرخه حیات فرگمنت هست را اضافه و درون آن آبجکت ساخته شده از View Binding را null می‌کنم تا هنگام حذف فرگمنت از اکتیویتی، این آبجکت نال شود.<br />
یک متد setText برای TextView فرگمنت در onCreateView و قبل از دستور return تعریف می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    
    //return inflater.inflate(R.layout.fragment_test, container, false);

    frgBinding = FragmentTestBinding.inflate(inflater, container, false);

    frgBinding.fragmentTxt.setText(&quot;متن جدید فرگمنت&quot;);

    return frgBinding.getRoot();

}
</pre>
<p>در نهایت، فرگمنت را در activity_main.xml تعریف می‌کنم. از اختصاص id به تگ fragment فراموش نکنید!</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;Button
        android:id=&quot;@+id/set_btn&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;80dp&quot;
        android:text=&quot;جایگذاری متن&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/txt_view&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;252dp&quot;
        android:text=&quot;Hello World!&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/set_btn&quot; /&gt;

    &lt;include
        android:id=&quot;@+id/include_layout&quot;
        layout=&quot;@layout/layout_bottom&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/txt_view&quot;
        tools:layout_editor_absoluteX=&quot;-16dp&quot; /&gt;

    &lt;fragment
        android:id=&quot;@+id/fragment_one&quot;
        android:name=&quot;ir.android_studio.viewbinding.TestFragment&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/include_layout&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>با اجرای مجدد پروژه می‌بینیم که متن موجود در setText روی TextView جایگزین متن پیش فرض آن شده:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/use_View_Binding_in_fragment.png" alt="پیاده سازی View Binding در فرگمنت" /><figcaption>پیاده سازی View Binding در فرگمنت</figcaption></figure>
<p>این جلسه هم به پایان رسید.<br />
موفق و پیروز باشید.</p>
<h3>مطالعه‌ی بیشتر:</h3>
<p style="text-align: left; direction: ltr;">
<a href="https://developer.android.com/topic/libraries/view-binding" target="_blank" rel="noopener">https://developer.android.com/topic/libraries/view-binding</a><br />
<a href="https://developer.android.com/topic/libraries/data-binding" target="_blank" rel="noopener">https://developer.android.com/topic/libraries/data-binding</a>
</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۲۸<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/05_4_ViewBinding.zip" class="button green edd-submit">دانلود رایگان با حجم ۲ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/05_4_ViewBinding.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/android-view-binding/">خداحافظ findViewById؛ سلام View Binding</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/android-view-binding/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
			</item>
		<item>
		<title>دریافت پیامک (SMS) در برنامه اندرویدی + سورس پروژه</title>
		<link>https://android-studio.ir/receive-sms-in-android-programming/</link>
					<comments>https://android-studio.ir/receive-sms-in-android-programming/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Mon, 31 May 2021 13:34:18 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=197009</guid>

					<description><![CDATA[<p>کاربردهای متعددی را برای قابلیت دریافت پیامک (SMS) در برنامه نویسی اندروید می‌توان برشمرد. با گسترش کاربرد اپلیکیشن‌های موبایلی و بویژه اندرویدی، حجم زیادی از خدمات بخش خصوصی و سازمانی در این محیط به مردم ارائه می‌گردد. برای مثال احراز هویت کاربر/مشتری یکی از آیتم‌های ضروری در ارائه این خدمات به شمار می‌رود که ارسال [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/receive-sms-in-android-programming/">دریافت پیامک (SMS) در برنامه اندرویدی + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/receive_sms_in_android_programming.png" alt="دریافت پیامک (SMS) در برنامه نویسی اندروید" /><br />
کاربردهای متعددی را برای قابلیت دریافت پیامک (SMS) در برنامه نویسی اندروید می‌توان برشمرد. با گسترش کاربرد اپلیکیشن‌های موبایلی و بویژه اندرویدی، حجم زیادی از خدمات بخش خصوصی و سازمانی در این محیط به مردم ارائه می‌گردد.<br />
برای مثال احراز هویت کاربر/مشتری یکی از آیتم‌های ضروری در ارائه این خدمات به شمار می‌رود که ارسال پیامک کد تایید به شماره همراه شخص یکی از رایج‌ترین روش‌هاست. اما برای بهبود UX (تجربه کاربری) می‌توان این بخش را به صورت خودکار انجام داد تا کاربر ملزم به وارد کردن دستی کد دریافتی نبوده و کد موجود در پیامک به طور خوکار وارد برنامه شود.<br />
البته کاربردهای دیگری هم برای دسترسی به پیامک‌های دریافتی وجود دارد و محدود به احراز هویت نیست. در این جلسه نحوه‌ی دریافت پیامک (SMS) در برنامه نویسی اندروید را به دو روش متفاوت بررسی می‌کنیم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#how-to-get-sms-in-android">نحوه‌ دریافت پیامک در اپلیکیشن اندرویدی</a></li>
<li><a href="#android-receive-sms-project">ساخت پروژه دریافت SMS در اندروید استودیو</a>
<ul>
<li><a href="#receive-sms-using-SmsMessage">دریافت پیامک (SMS) توسط SmsMessage</a></li>
<li><a href="#receive-sms-using-Cursor">دریافت پیامک (SMS) توسط Cursor</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="how-to-get-sms-in-android">نحوه‌ دریافت پیامک در اپلیکیشن اندرویدی</h2>
<p>به نام خدا. به طور کل با استفاده از دو کلاس SmsMessage و Cursor می‌توانیم به پیامک‌های موجود در دستگاه اندرویدی دسترسی داشته باشیم. البته روش متداول، استفاده از SmsMessage است. در ادامه به بررسی هر دو روش پرداخته و سپس به صورت عملی پیاده سازی می‌کنیم.<br />
<b>SmsMessage:</b> کلاسی با نام SmsMessage در API 4 به <a href="http://android-studio.ir/introduction-android-operating-system/" target="_blank" rel="noopener">سیستم عامل اندروید</a> اضافه شده که وظیفه دسترسی به پیامک یا همان SMS را در دستگاه‌های اندرویدی بر عهده دارد.<br />
البته از API 1 هم کلاسی با این نام وجود داشت (<a href="https://developer.android.com/reference/android/telephony/gsm/SmsMessage" target="_blank" rel="noopener">android.telephony.gsm.SmsMessage</a>) که تنها از پروتکل GSM پشتیبانی می‌کرد. اما در API 4 این کلاس با <a href="https://developer.android.com/reference/android/telephony/SmsMessage" target="_blank" rel="noopener">android.telephony.SmsMessage</a> جایگزین شد که از هر دو پروتکل GSM و  CDMA پشتیبانی می‌کند.<br />
همچنین برای شنود رویداد مربوط به دریافت پیامک لازم است از برودکست رسیور استفاده کنیم که قبلا در <a href="http://android-studio.ir/android-broadcastreceiver-component/" target="_blank" rel="noopener">آموزش BroadcastReceiver در اندروید</a> با این مبحث آشنا شدیم.<br />
<b>Cursor:</b> علاوه بر کلاس SmsMessage با استفاده از کلاس Cursor هم می‌توان به آرشیو پیامک‌ها دسترسی داشت که با وجود کلاس SmsMessage استفاده از آن جایگاهی نخواهد داشت با این حال در انتهای آموزش به این مورد نیز اشاره مختصری خواهیم کرد.</p>
<h2 id="android-receive-sms-project">ساخت پروژه دریافت SMS در اندروید استودیو</h2>
<p>برای بررسی نحوه دریافت پیامک (SMS) در برنامه نویسی اندروید مطابق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام ReceiveSMS می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.</p>
<h3 id="receive-sms-using-SmsMessage">دریافت پیامک (SMS) توسط SmsMessage</h3>
<p>اگر بخاطر داشته باشید در جلسه <a href="http://android-studio.ir/send-sms-in-android-programming/" target="_blank" rel="noopener">نحوه ارسال پیامک (SMS) در برنامه نویسی اندروید</a> در مانیفست پروژه مجوز ارسال پیامک را تعریف کردیم. در این پروژه لازم است عکس آن یعنی مجوز دریافت SMS را تعریف کنیم:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;uses-permission android:name=&quot;android.permission.RECEIVE_SMS&quot; /&gt;
</pre>
<p><span class="filename">AndroidManifest.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.receivesms&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.RECEIVE_SMS&quot; /&gt;

    &lt;application
        android:allowBackup=&quot;true&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/Theme.ReceiveSMS&quot;&gt;
        &lt;activity android:name=&quot;.MainActivity&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;

&lt;/manifest&gt;
</pre>
<p>در قدم بعد لازم است یک BroadcastReceiver ایجاد کنم. بر خلاف پروژه ارسال SMS که برودکست درون اکتیویتی و به صورت داینامیک رجیستر شد، در این جلسه برودکست را در کلاس مجزا تعریف کرده و همچنین رجیستر یا ثبت آن را به صورت استاتیک و درون Manifest انجام می‌دهم.<br />
یک کلاس با نام SMSBroadcastReceiver به پروژه اضافه کرده و آنرا از BroadcastReceiver مشتق می‌کنم. سپس متد onReceive را اضافه می‌کنم:</p>
<p><span class="filename">SMSBroadcastReceiver.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.receivesms;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class SMSBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        
    }
}
</pre>
<p>سپس برودکست رسیور را در مانیفست پروژه ثبت می‌کنم:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;receiver android:name=&quot;.SMSBroadcastReceiver&quot;&gt;
    &lt;intent-filter&gt;
        &lt;action android:name=&quot;android.provider.Telephony.SMS_RECEIVED&quot; /&gt;
    &lt;/intent-filter&gt;
&lt;/receiver&gt;
</pre>
<p>کد کامل مانیفست:<br />
<span class="filename">AndroidManifest.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.receivesms&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.RECEIVE_SMS&quot; /&gt;

    &lt;application
        android:allowBackup=&quot;true&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/Theme.ReceiveSMS&quot;&gt;
        &lt;activity android:name=&quot;.MainActivity&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
        &lt;receiver android:name=&quot;.SMSBroadcastReceiver&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.provider.Telephony.SMS_RECEIVED&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/receiver&gt;
    &lt;/application&gt;

&lt;/manifest&gt;
</pre>
<p>یک اکشن با نام android.provider.Telephony.SMS_RECEIVED در مانیفست تعریف کردیم. این اکشن وظیفه بررسی رویدادهای مربوط به دریافت پیامک را در سیستم عامل بر عهده دارد.<br />
قبل از پیاده سازی کلاس SmsMessage قصد دارم صرفا عملکرد برودکست را بررسی کنم و مطمئن شوم action ای که در مانیفست تعریف کردم می‌تواند به دریافت پیامک واکنش نشان دهد. بنابراین در متد onReceive برودکست رسیور صرفا یک پیغام Toast تعریف می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
public void onReceive(Context context, Intent intent) {

    Toast.makeText(context, &quot;یک پیامک دریافت شد&quot;, Toast.LENGTH_SHORT).show();

}
</pre>
<p>با توجه به اینکه قصد دارم پروژه را روی یک دیوایس بالاتر از اندروید ۶ اجرا کنم، لازم است کدهای مربوط به Runtime Permission را مطابق <a href="http://android-studio.ir/android-runtime-permission/" target="_blank" rel="noopener">آموزش Runtime Permission در اندروید</a> به اکتیویتی اضافه کنم:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.receivesms;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private final int SMS_REQUEST_CODE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED) {

            requestReceiveSMSpermission();

        } else {

            Toast.makeText(this, &quot;مجوز قبلا دریافت شده&quot;, Toast.LENGTH_SHORT).show();

        }

    }

    private void requestReceiveSMSpermission() {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.RECEIVE_SMS)) {

            new AlertDialog.Builder(this)
                    .setTitle(&quot;درخواست مجوز&quot;)
                    .setMessage(&quot;برای عملکرد صحیح برنامه باید دسترسی به دریافت پیامکتایید شود&quot;)
                    .setPositiveButton(&quot;موافقم&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            reqPermission();

                        }
                    })
                    .setNegativeButton(&quot;لغو&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            dialogInterface.dismiss();

                        }
                    })
                    .create()
                    .show();

        } else {

            reqPermission();

        }

    }

    private void reqPermission() {

        ActivityCompat.requestPermissions(MainActivity.this, new String&#x5B;] {Manifest.permission.RECEIVE_SMS}, SMS_REQUEST_CODE);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {

        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == SMS_REQUEST_CODE) {

            if (grantResults.length &gt; 0 &amp;&amp; grantResults&#x5B;0] == PackageManager.PERMISSION_GRANTED) {

                Toast.makeText(this, &quot;مجوز تایید شد&quot;, Toast.LENGTH_SHORT).show();

            } else {

                Toast.makeText(this, &quot;مجوز رد شد&quot;, Toast.LENGTH_SHORT).show();

            }

        }

    }

}
</pre>
<p>یکی از دیوایس‌های <a href="http://android-studio.ir/install-genymotion/" target="_blank" rel="noopener">امولاتور اندرویدی Genymotion</a> را اجرا می‌کنم. یکی از امکانات کاربردی شبیه سازهای اندرویدی از جمله Genymotion و همچنین AVD (شبیه ساز داخلی اندروید استودیو) قابلیت ارسال پیامک به دستگاه و همچنین برقراری تماس است. بدون آنکه نیاز به اجرا و تست برنامه روی یک دستگاه واقعی داشته باشیم.<br />
پروژه را روی شبیه ساز اجرا کرده و مجوز دسترسی به پیامک‌ها را برای برنامه تایید می‌کنم. سپس روی آیکون Phone منوی سمت راست دیوایس کلیک می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/confirm_RECEIVE_SMS_permission.png" alt="تایید مجوز دسترسی به پیامک‌ها در اندروید" /><figcaption>تایید مجوز دسترسی به پیامک‌ها در اندروید</figcaption></figure>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/send_sms_in_android_emulator.png" alt="قابلیت ارسال SMS در امولاتور Genymotion" /><figcaption>قابلیت ارسال SMS در امولاتور Genymotion</figcaption></figure>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/send_sms_in_android_emulator_2.png" alt="ارسال پیامک آزمایشی در شبیه ساز اندروید" /><figcaption>ارسال پیامک آزمایشی در شبیه ساز اندروید</figcaption></figure>
<p>در کادر Phone یک شماره فرستنده و متن دلخواه وارد کرده و پیام را ارسال می‌کنم. بلافاصله پیغام Toast ای که در متد onReceive تعریف کرده بودم اجرا شد:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/sent_sms_received_by_BroadcastReceiver.png" alt="دریافت رویداد پیامک توسط BroadcastReceiver" /><figcaption>دریافت رویداد پیامک توسط BroadcastReceiver</figcaption></figure>
<p>تا اینجای کار مطمئن شدیم برنامه ما می‌تواند به درستی رویدادهای مربوط به دریافت پیامک را شنود کند. در قدم بعد می‌خواهیم محتوای SMS دریافتی را به دست بیاوریم که شامل شماره ارسال کننده پیام و همچنین متن پیامک می‌باشد.<br />
پیغام Toast داخل متد onReceive برودکست رسیور را حذف کرده و کد زیر را جایگزین آن می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
if (intent.getAction().equals(&quot;android.provider.Telephony.SMS_RECEIVED&quot;)) {

    Bundle mBundle = intent.getExtras();
    SmsMessage&#x5B;] msg;
    String smsFrom;

    if (mBundle != null) {
        try {
            Object&#x5B;] mPdus = (Object&#x5B;]) mBundle.get(&quot;pdus&quot;);
            msg = new SmsMessage&#x5B;mPdus.length];

            for (int i = 0; i &lt; mPdus.length; i++) {
                msg&#x5B;i] = SmsMessage.createFromPdu((byte&#x5B;]) mPdus&#x5B;i]);
                smsFrom = msg&#x5B;i].getOriginatingAddress();
                String smsBody = msg&#x5B;i].getMessageBody();

                Toast.makeText(context, &quot;شماره: &quot; + smsFrom + &quot; / پیام: &quot; + smsBody, Toast.LENGTH_SHORT).show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
</pre>
<p>در ابتدا یک if تعریف شده که بررسی می‌کند کدها تنها در صورتی اجرا شوند که <a href="http://android-studio.ir/intent/" target="_blank" rel="noopener">اینتنت اکشن</a> دریافتی از نوع android.provider.Telephony.SMS_RECEIVED باشد. یعنی دقیقا همان اکشنی که قبلا در AndroidManifest.xml و در تگ receiver ثبت کردیم.<br />
سپس یک نمونه از Bundle و SmsMessage و یک String ساخته شده. در ادامه ابتدا بررسی می‌شود آیا مقدار ذخیره شده در mBundle نال است یا نه؛ یعنی داده‌ای که مربوط به محتوای یک پیامک باشد در باندل ذخیره شده یا خیر.<br />
یک نمونه از <span dir="ltr">Object[]</span> با نام دلخواه mPdus ایجاد شده که داده‌ها با فرمت PDU (مخفف Protocol Data Unit) در آن ذخیره می‌شود. با استفاده از PDU به ساختار محتوای پیام دریافتی دسترسی داریم.<br />
سپس در حلقه for با استفاده از <span dir="ltr">SmsMessage.createFromPdu()</span> محتوای پیام دریافتی تفکیک می‌شود. همانطور که ملاحظه می‌کنید در دو خط بعد توسط getOriginatingAddress و getMessageBody به ترتیب به شماره ارسال کننده پیام و متن پیام دسترسی خواهیم داشت. کاربرد متدها از نحوه نامگذاری آنها هم مشخص است. عبارت Originating Address به معنی آدرس مبدا و Message Body به معنی بدنه یا متن پیام است.<br />
در انتها، این دو آیتم در یک پیغام Toast قرار داده شده است.<br />
کد کامل کلاس برودکست رسیور:</p>
<p><span class="filename">SMSBroadcastReceiver.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.receivesms;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.widget.Toast;

public class SMSBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        // Toast.makeText(context, &quot;یک پیامک دریافت شد&quot;, Toast.LENGTH_SHORT).show();

        if (intent.getAction().equals(&quot;android.provider.Telephony.SMS_RECEIVED&quot;)) {

            Bundle mBundle = intent.getExtras();
            SmsMessage&#x5B;] msg;
            String smsFrom;

            if (mBundle != null) {
                try {
                    Object&#x5B;] mPdus = (Object&#x5B;]) mBundle.get(&quot;pdus&quot;);
                    msg = new SmsMessage&#x5B;mPdus.length];

                    for (int i = 0; i &lt; mPdus.length; i++) {
                        msg&#x5B;i] = SmsMessage.createFromPdu((byte&#x5B;]) mPdus&#x5B;i]);
                        smsFrom = msg&#x5B;i].getOriginatingAddress();
                        String smsBody = msg&#x5B;i].getMessageBody();

                        Toast.makeText(context, &quot;شماره: &quot; + smsFrom + &quot; / پیام: &quot; + smsBody, Toast.LENGTH_SHORT).show();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }

    }
}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> در کد فوق، متد createFromPdu دو پارامتر ورودی به صورت</p>
<p style="text-align: left; direction: ltr;">
createFromPdu(byte[] pdu, String format)
</p>
<p>می‌گیرد که تا قبل از API 23 دارای یک پارامتر ورودی و به صورت </p>
<p style="text-align: left; direction: ltr;">
createFromPdu(byte[] pdu)
</p>
<p>بوده است. اما نیازی به تعریف هردو متد بر اساس نسخه اندروید کاربر نیست. من پروژه را بر روی اندروید ۴ به بالا تست کردم و اختلالی در دریافت SMS در نسخه‌های قدیمی وجود نداشت.
</p></div>
<p>پروژه را دوباره اجرا کرده و با استفاده از گزینه Phone یک پیام جدید ارسال می‌کنم. حالا شماره ارسال کننده و متن SMS هم در Toast نمایش داده می‌شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/get_SMS_OriginatingAddress_&#038;_MessageBody.png" alt="دریافت شماره مبدا و متن پیامک توسط SmsMessage" /><figcaption>دریافت شماره مبدا و متن پیامک توسط SmsMessage</figcaption></figure>
<h3 id="receive-sms-using-Cursor">دریافت پیامک (SMS) توسط Cursor</h3>
<p>همانطور که در ابتدای جلسه اشاره شد، با وجود کلاس SmsMessage استفاده از Cursor برای دریافت پیامک توجیهی نخواهد داشت اما لازم دانستم حداقل برای تمرین به این مورد هم اشاره کنم.<br />
یک پروژه جدید بسازید و TextView پیش فرض اکتیویتی را به Button تبدیل کنید. سپس کد زیر را در رویداد onClickListener دکمه قرار دهید (از تعریف Runtime Permission فراموش نکنید).</p>
<pre class="brush: java; title: ; notranslate">
Cursor cursor = getContentResolver().query(Uri.parse(&quot;content://sms&quot;), null, null, null, null);

cursor.moveToFirst();

Toast.makeText(this, cursor.getString(12), Toast.LENGTH_SHORT).show();
</pre>
<p>بعد از اجرای پروژه و تایید مجوز دریافت SMS، مانند قبل یک پیامک ارسال کرده و سپس روی Button کلیک کنید. متن پیامک در قالب Toast نمایش داده خواهد شد. البته در این روش هم می‌توان شماره ارسال کننده پیامک را دریافت کرد که به آن نمی‌پردازم.</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۱۳<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/14_2_ReceiveSMS.zip" class="button green edd-submit">دانلود رایگان با حجم ۲ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/14_2_ReceiveSMS.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/receive-sms-in-android-programming/">دریافت پیامک (SMS) در برنامه اندرویدی + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/receive-sms-in-android-programming/feed/</wfw:commentRss>
			<slash:comments>15</slash:comments>
		
		
			</item>
		<item>
		<title>ارسال پیامک (SMS) در برنامه اندرویدی + سورس پروژه</title>
		<link>https://android-studio.ir/send-sms-in-android-programming/</link>
					<comments>https://android-studio.ir/send-sms-in-android-programming/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Tue, 04 May 2021 17:18:52 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=196576</guid>

					<description><![CDATA[<p>یکی از قابلیت‌هایی که در برخی از برنامه‌های اندرویدی مورد استفاده قرار می‌گیرد، ارسال پیامک است. مواردی مانند ارسال داده‌های کم حجم، ارسال موقعیت جغرافیایی و&#8230; از کاربردهای این قابلیت بشمار می‌رود. در این جلسه به نحوه ارسال پیامک (SMS) در برنامه نویسی اندروید می‌پردازیم. همچنین بررسی وضعیت سیم کارت (فعال یا غیر فعال بودن [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/send-sms-in-android-programming/">ارسال پیامک (SMS) در برنامه اندرویدی + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/send_sms_in_android_programming.png" alt="آموزش ارسال پیامک (SMS) در برنامه نویسی اندروید" /><br />
یکی از قابلیت‌هایی که در برخی از برنامه‌های اندرویدی مورد استفاده قرار می‌گیرد، ارسال پیامک است. مواردی مانند ارسال داده‌های کم حجم، ارسال موقعیت جغرافیایی و&#8230; از کاربردهای این قابلیت بشمار می‌رود. در این جلسه به نحوه ارسال پیامک (SMS) در برنامه نویسی اندروید می‌پردازیم.<br />
همچنین بررسی وضعیت سیم کارت (فعال یا غیر فعال بودن و&#8230;) و دریافت گزارش ارسال و گزارش تحویل پیامک از دیگر مواردی است که در این جلسه به آن پرداخته خواهد شد.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#send-sms-from-android-app">کاربردهای ارسال پیامک از طریق اپلیکیشن</a></li>
<li><a href="#how-to-send-sms-in-android-app">نحوه ارسال پیامک در اندروید</a>
<ul>
<li><a href="#send-sms-using-intent">ارسال پیامک (SMS) از طریق Intent</a></li>
<li><a href="#send-sms-using-smsMamager">ارسال پیامک (SMS) از طریق کلاس SmsManager</a></li>
</ul>
</li>
<li><a href="#android-send-sms-project">ساخت پروژه اندرویدی ارسال پیامک</a>
<ul>
<li><a href="#send-sms-permission">تعریف مجوز ارسال پیامک</a></li>
<li><a href="#smsManager-class">پیاده سازی کلاس SmsManager</a></li>
<li><a href="#send-&#038;-delivery-report-in-smsManager">دریافت گزارش ارسال و گزارش تحویل پیامک</a></li>
<li><a href="#check-sim-state-before-sending-sms">بررسی وضعیت سیم کارت قبل از ارسال پیامک</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="send-sms-from-android-app">کاربردهای ارسال پیامک از طریق اپلیکیشن</h2>
<p>به نام خدا. ارسال پیامک از طریق برنامه‌های اندرویدی کاربردهای متفاوتی می‌تواند داشته باشد. برای مثال در برخی از همراه بانک‌ها کاربر امکان انتخاب اینترنت یا پیامک (SMS) را به عنوان بستر ارسال و دریافت اطلاعات در اختیار دارد.<br />
همچنین یکی دیگر از کاربردهای ارسال پیامک (SMS) در برنامه نویسی اندروید ارسال موقعیت جغرافیایی فرد (بر اساس مختصات GPS) به یک شماره خاص است. قابلیتی که به فرد این امکان را می‌دهد تا در شرایط اضطراری، با لمس یک دکمه موقعیت جغرافیایی خود را برای شماره موبایلی که قبلا در برنامه تعریف کرده ارسال کند. یا برنامه‌ای که بر روی دستگاه اندرویدی کودکان نصب می‌شود تا در فاصله‌های زمانی معین، مختصات جغرافیایی او را به صورت خودکار به شماره همراه والدینش ارسال می‌کند.<br />
ارسال پیامک به مرکز سرویس دهنده‌ی اپلیکیشن به جهت تائید شماره همراه کاربر در هنگام عضویت یکی دیگر از کاربردهای این قابلیت است.<br />
قابلیت ارسال پیامک از اپلیکیشن کاربردهای متعدد دیگری می‌تواند داشته باشد که استفاده از آن به ماهیت برنامه بستگی دارد.</p>
<h2 id="how-to-send-sms-in-android-app">نحوه ارسال پیامک در اندروید</h2>
<p>ارسال پیامک (SMS) در برنامه نویسی اندروید به دو طریق امکان پذیر است. روش نخست استفاده از intent است که در صورت پیاده سازی این روش در برنامه، کاربر برای ارسال پیامک به برنامه مدیریت پیامک پیش فرض دستگاه خود منتقل خواهد شد.<br />
اما در روش دوم با استفاده از کلاس SmsManager فرایند ارسال پیامک درون خود برنامه و در پشت صحنه انجام شده و کاربر از جابجایی بین برنامه اصلی و برنامه مدیریت SMS خود بی نیاز خواهد بود. علاوه بر آن دسترسی به محتوای پیامک و امکان ویرایش آن نیز مقدور نیست.<br />
در ادامه جلسه به بررسی هردو روش می‌پردازیم.</p>
<h3 id="send-sms-using-intent">ارسال پیامک (SMS) از طریق Intent</h3>
<p>قبلا در جلسه <a href="http://android-studio.ir/intent/" target="_blank" rel="noopener">آموزش کار با intent در اندروید</a> با کاربرد اینتنت‌ها در برنامه نویسی اندروید آشنا شدیم. ضمن اینکه در همان جلسه یکی از مثال‌های کاربرد intent به قابلیت ارسال پیامک از طریق برنامه مدیریت SMS پیش فرض دستگاه اختصاص یافته بود که توضیحات کامل را می‌توانید مطالعه کنید. بنابراین از پرداختن مجدد به توضیحات این روش اجتناب می‌کنم و صرفا جهت یاداوری، کد اینتنت مربوط به ارسال پیامک از طریق برنامه مدیریت پیامک را در اینجا ذکر می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
public void contactMessage(View v) {
    Intent cnt_msg = new Intent();
    cnt_msg.setAction(Intent.ACTION_VIEW);
    cnt_msg.setData(Uri.parse(&quot;sms:+989158888888&quot;));
    cnt_msg.putExtra(&quot;sms_body&quot;, &quot;سلام. در خصوص اپلیکیشن شرکتتون یه پیشنهاد داشتم&quot;);
    startActivity(cnt_msg);
}
</pre>
<p>با اجرای تابع فوق، کاربر به برنامه پیامک پیش فرض دیوایس اندرویدی منتقل شده و شماره‌ی گیرنده پیام و همچنین متن پیامک به صورت خودکار اضافه می‌شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/send_sms_in_android_using_intent.png" alt="ارسال پیامک (SMS) در اندروید توسط intent" /><figcaption>ارسال پیامک (SMS) در اندروید توسط intent</figcaption></figure>
<p>در اینجا کاربر در صورت تمایل قبل از ارسال پیامک می‌تواند محتوای پیام را ویرایش کند.<br />
اما این شیوه ارسال پیامک به دلیل طولانی بودن فرایند ارسال و همچنین دسترسی کاربر به محتوای آن کاربرد چندانی نداشته و برنامه نویسان اندرویدی عموما روش دوم را بکار می‌گیرند که در ادامه مبحث معرفی می‌کنم.</p>
<h3 id="send-sms-using-smsMamager">ارسال پیامک (SMS) از طریق کلاس SmsManager</h3>
<p>بر خلاف روش قبل یعنی ارسال پیامک از طریق intent در این روش و با استفاده از کلاس SmsManager پیامک به طور مستقیم و درون خود برنامه به مقصد ارسال شده و کاربر دسترسی به محتوای پیامک نخواهد داشت. به عبارت دیگر، کاربر از فرایند ارسال پیامک آگاه نخواهد شد.<br />
با استفاده از SmsManager می‌توان ۴ نوع پیام را ارسال کرد که لازم است کاربرد هرکدام را توضیح دهم:<br />
<b>sendTextMessage:</b> همانطور که از نام این متد پیداست، برای ارسال پیامک (SMS) معمولی بکار می‌رود. یعنی ارسال پیامک متنی در قالب یک پیام و به مقصد از قبل تعیین شده. درست همان چیزی که در این جلسه نیاز داریم.<br />
<b>sendMultipartTextMessage:</b> عملیات مشابه متد قبل را انجام می‌دهد با این تفاوت که می‌توان پیام را در قالب چند پیام جداگانه به مقصد ارسال کرد.<br />
<b>sendDataMessage:</b> از این متد برای ارسال پیام داده محور و در یک پورت مشخص استفاده می‌شود.<br />
<b>sendMultimediaMessage:</b> چنانچه قصد ارسال پیام مولتی مدیا یا همان MMS (مانند ارسال عکس) داشته باشیم باید از این متد استفاده کنیم.</p>
<p>همانطور که در توضیحات قبل اشاره شد، در این جلسه می‌خواهیم یک پیامک متنی ساده و تک قسمتی را ارسال کنیم که متد sendTextMessage مناسب این کار است. با ادامه جلسه همراه باشید.</p>
<h2 id="android-send-sms-project">ساخت پروژه اندرویدی ارسال پیامک</h2>
<p>مطابق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام SendSMS می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.<br />
در این پروژه قصد داریم با استفاده از کلاس SmsManager بصورت درون برنامه‌ای و بدون نیاز به برنامه مدیریت پیامک، یک پیام کوتاه به مقصدی مشخص ارسال کنیم. همچنین نحوه فعالسازی گزارش ارسال (sent) و گزارش تحویل (delivery) پیامک و در دسترس بودن یا نبودن سیم کارت جهت ارسال SMS را نیز بررسی خواهیم کرد.<br />
در رابط کاربری این پروژه تنها به یک دکمه برای ارسال متن پیامک از پیش تعیین شده نیاز داریم. بنابراین در layout پیش فرض اکتیویتی پروژه کافیست TextView موجود در مرکز ویو گروپ ConstraintLayout را به Button تبدیل کرده و یک id به آن اختصاص دهم:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;Button
        android:id=&quot;@+id/sms_btn&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;ارسال پیامک&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>اگر با این layout آشنایی ندارید <a href="http://android-studio.ir/constraintlayout/" target="_blank" rel="noopener">آموزش کار با ConstraintLayout</a> را مطالعه کنید. حالا رویداد مربوط به دکمه را در اکتیویتی تعریف می‌کنم:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.sendsms;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    Button sendBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendBtn = findViewById(R.id.sms_btn);

        sendBtn.setOnClickListener(view -&gt; {

        });

    }
}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> در نسخه‌های اخیر اندروید استودیو، هنگام اضافه کردن متد setOnClickListener پیشنهادی مبنی بر جایگزینی View.OnClickListener با lambda داده می‌شود که با زدن alt+Enter یا کلیک روی آیکون لامپ، جایگزینی انجام می‌شود. تفاوت در کوتاه شدن کدهاست.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/replace_OnClickListener_with_lambda.png" alt="تبدیل setOnClickListener به lambda" /><figcaption>تبدیل setOnClickListener به lambda</figcaption></figure>
<p>پس از تبدیل، کد رویداد دکمه به اینصورت تبدیل می‌شود:</p>
<pre class="brush: java; title: ; notranslate">
sendBtn.setOnClickListener(view -&gt; {

});
</pre>
</div>
<h3 id="send-sms-permission">تعریف مجوز ارسال پیامک</h3>
<p>در قدم اول لازم است مجوز یا Permission ارسال پیامک (SMS) را در برنامه تعریف کنیم. ابتدا پرمیشن SEND_SMS را در مانیفست پروژه تعریف می‌کنم:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;uses-permission android:name=&quot;android.permission.SEND_SMS&quot;/&gt;
</pre>
<p><span class="filename">AndroidManifest.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.sendsms&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.SEND_SMS&quot;/&gt;

    &lt;application
        android:allowBackup=&quot;true&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/Theme.SendSMS&quot;&gt;
        &lt;activity android:name=&quot;.MainActivity&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;

&lt;/manifest&gt;
</pre>
<p>همچنین مطابق جلسه <a href="http://android-studio.ir/android-runtime-permission/" target="_blank" rel="noopener">آموزش پیاده سازی Runtime Permission</a> برای دریافت مجوز در دستگاه‌های اندرویدی ۶ و به بالا کدهای لازم را به پروژه اضافه می‌کنم:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.sendsms;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    Button sendBtn;
    private final int SMS_REQUEST_CODE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendBtn = findViewById(R.id.sms_btn);

        sendBtn.setOnClickListener(view -&gt; {

            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
                
                requestSendSMSpermission();
                
            } else {
                
                sendMessage();
                
            }

        });

    }

    private void requestSendSMSpermission() {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.SEND_SMS)) {

            new AlertDialog.Builder(this)
                    .setTitle(&quot;درخواست مجوز&quot;)
                    .setMessage(&quot;برای عملکرد صحیح برنامه باید دسترسی به ارسال پیامک تایید شود&quot;)
                    .setPositiveButton(&quot;موافقم&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            reqPermission();

                        }
                    })
                    .setNegativeButton(&quot;لغو&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            dialogInterface.dismiss();

                        }
                    })
                    .create()
                    .show();

        } else {

            reqPermission();

        }

    }

    private void reqPermission() {

        ActivityCompat.requestPermissions(MainActivity.this, new String&#x5B;] {Manifest.permission.SEND_SMS}, SMS_REQUEST_CODE);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {

        if (requestCode == SMS_REQUEST_CODE) {

            if (grantResults.length &gt; 0 &amp;&amp; grantResults&#x5B;0] == PackageManager.PERMISSION_GRANTED) {
                
                sendMessage();
                
            } else {
                
                Toast.makeText(this, &quot;مجوز رد شد&quot;, Toast.LENGTH_SHORT).show();
                
            }

        }

    }
    
}
</pre>
<h3 id="smsManager-class">پیاده سازی کلاس SmsManager</h3>
<p>با اضافه شدن کدهای فوق، پس از لمس دکمه &#8220;ارسال پیامک&#8221; چنانچه کاربر مجوز ارسال SMS را به برنامه اعطا کند، تابع sendMessage فراخوانی خواهد شد. تابعی با همین نام درون اکتیویتی و بعد از متد onCreate اضافه کرده و کدهای مربوط به ارسال پیامک را درون آن می‌نویسم:</p>
<pre class="brush: java; title: ; notranslate">
private void sendMessage() {

    try {
        
        SmsManager smsManager = SmsManager.getDefault();
        smsManager.sendTextMessage(&quot;+989158888888&quot;, null, &quot;تست ارسال پیامک&quot;, null, null);

        Toast.makeText(MainActivity.this, &quot; پیامک ارسال شد&quot;, Toast.LENGTH_SHORT).show();
        
    } catch (Exception e) {
        
        Toast.makeText(MainActivity.this, &quot; پیامک ارسال نشد&quot;, Toast.LENGTH_SHORT).show();
        
    }

}
</pre>
<p>کدهای مربوط به ارسال پیامک را درون یک try catch قرار داده‌ام تا چنانچه به هر دلیلی فرایند ارسال SMS انجام نشد، برنامه کرش نکند.<br />
در کد فوق ابتدا یک نمونه (شیء) از کلاس SmsManager با نام دلخواه smsManager ایجاد شده که برای مقدار دهی آن، متد getDefault فراخوانی شده است. این متد شناسه سیم کارت پیش فرض دستگاه اندرویدی را دریافت می‌کند که برای ارسال پیامک ضروری است. توصیه می‌کنم توضیحات تکمیلی <a href="https://developer.android.com/reference/android/telephony/SmsManager#sendTextMessage(java.lang.String,%20java.lang.String,%20java.lang.String,%20android.app.PendingIntent,%20android.app.PendingIntent)" target="_blank" rel="noopener">اینجا</a> و <a href="https://developer.android.com/reference/android/telephony/SmsManager#getDefault()" target="_blank" rel="noopener">اینجا</a> را در خصوص دستگاه‌های دو سیم کارت مطالعه کنید.<br />
در خط دوم، متد sendTextMessage تعریف شده که ۴ پارامتر ورودی می‌پذیرد. در تصویر زیر جنس هر ورودی قابل تشخیص است:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sendTextMessage_method_parameters.png" alt="پارامترهای ورودی sendTextMessage" /><figcaption>پارامترهای ورودی sendTextMessage</figcaption></figure>
<p><b>destinationAddress:</b> پارامتر نخست از جنس String بوده که همان شماره گیرنده پیام می‌باشد.<br />
<b>scAddress:</b> پارامتر دوم مربوط به آدرس یا شماره‌ی Service Center پیامک اپراتوری است که کاربر از سیم کارت آن استفاده می‌کند. به طور معمول این شماره توسط کلاینت تعیین نمی‌شود و به عهده‌ی خود اپراتور است. بنابراین مقدار null را وارد می‌کنیم.<br />
<b>text:</b> واضح است که این پارامتر، محتوای پیام را شامل می‌شود.<br />
<b>sentIntent و deliveryIntent:</b> به ترتیب برای دریافت گزارش ارسال و گزارش تحویل پیامک استفاده می‌شود که در این مرحله به جهت درک بهتر عملکرد اصلی کلاس، برای هردو مقدار null درنظر گرفتم. بنابراین در این مرحله به نتیجه‌ی ارسال و یا تحویل پیامک به مقصد دسترسی نخواهیم داشت.<br />
نگران نباشید! به این دو مورد هم در ادامه جلسه می‌پردازیم.<br />
در همین مرحله پروژه را اجرا می‌کنم تا عملکرد کلاس SmsManager را بررسی کنیم.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/run_send_sms_android_project_on_emulator.png" alt="اجرای پروژه اندرویدی ارسال SMS روی شبیه ساز" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/send_sms_permission_granted.png" alt="تایید مجوز ارسال پیامک" /><figcaption>تایید مجوز ارسال پیامک</figcaption></figure>
<p>با توجه به اینکه یک دیوایس با Android 10 را روی <a href="http://android-studio.ir/install-genymotion/" target="_blank" rel="noopener">شبیه ساز (امولاتور) اندرویدی</a> اجرا کرده‌ام، با کلیک روی دکمه ارسال پیامک ابتدا باید مجوز ارسال پیامک را تایید کنم.<br />
با تایید مجوز، پیغام &#8220;پیامک ارسال شد&#8221; روی صفحه ظاهر شد که نشان می‌دهد عملکرد برنامه تا اینجای کار درست بوده:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sms_sent_using_SmsManager_class.png" alt="ارسال پیامک توسط کلاس SmsManager" /><figcaption>ارسال پیامک توسط کلاس SmsManager</figcaption></figure>
<p>پیامک ارسال شده در برنامه مدیریت SMS پیش فرض دستگاه نیز قابل مشاهده است:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sms_in_device_default_sms_app.png" alt="نمایش پیامک ارسال شده توسط SmsManager در برنامه پیامک پیش فرض دستگاه" /><figcaption>نمایش پیامک ارسال شده توسط SmsManager در برنامه پیامک پیش فرض دستگاه</figcaption></figure>
<h3 id="send-&#038;-delivery-report-in-smsManager">دریافت گزارش ارسال و گزارش تحویل پیامک</h3>
<p>در قسمت قبل صرفا یک SMS توسط کلاس SmsManager ارسال کردیم و دسترسی به گزارش ارسال و تحویل پیامک نداشتیم و برای هردو پارامتر sentIntent و deliveryIntent مقدار null داده بودیم.<br />
اما در این قسمت و در ادامه‌ی آموزش ارسال پیامک (SMS) در برنامه نویسی اندروید با استفاده از BroadcastReceiver و PendingIntent رویدادهای مربوط به وضعیت ارسال و تحویل پیامک را شنود کرده و پیغام مربوط آن را چاپ می‌کنیم.<br />
ابتدا در بدنه اصلی کلاس اکتیویتی دو متغیر از جنس String با نام و مقدار دلخواه تعریف می‌کنم. از این دو متغیر برای برقراری ارتباط بین BroadcastReceiver و PendingIntent مربوط به یکدیگر استفاده خواهیم کرد:</p>
<pre class="brush: java; title: ; notranslate">
String SMS_SENT = &quot;SMS_SENT&quot;;
String SMS_DELIVERED = &quot;SMS_DELIVERED&quot;;
</pre>
<p>سپس مطابق جلسه <a href="http://android-studio.ir/android-broadcastreceiver-component/" target="_blank" rel="noopener">آموزش شنود رویدادها در اندروید توسط BroadcastReceiver</a> دو برودکست را درون متد sendMessage داخل بلاک try و قبل از smsManager، رجیستر می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
//Broadcast for Sent SMS
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {

        String state = &quot;&quot;;
        switch (getResultCode()) {
            case Activity.RESULT_OK:
                state = &quot;پیامک ارسال شد&quot;;
                break;
            case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                state = &quot;یک خطای عمومی رخ داد&quot;;
                break;
            case SmsManager.RESULT_ERROR_NO_SERVICE:
                state = &quot;اپراتور در دسترس نیست&quot;;
                break;
            case SmsManager.RESULT_ERROR_NULL_PDU:
                state = &quot; پروتکل PDU در دسترس نیست&quot;;
                break;
            case SmsManager.RESULT_ERROR_RADIO_OFF:
                state = &quot;سیم کارت در دسترس نیست&quot;;
                break;
        }
        Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
    }
}, new IntentFilter(SMS_SENT));

//Broadcast for Delivered SMS
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String state = &quot;&quot;;
        switch (getResultCode()) {
            case Activity.RESULT_OK:
                state = &quot;پیامک تحویل داده شد&quot;;
                break;
            case Activity.RESULT_CANCELED:
                state = &quot;پیامک تحویل داده نشد&quot;;
                break;
        }
        Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
    }
}, new IntentFilter(SMS_DELIVERED));
</pre>
<p>برودکست اول برای شنود وضعیت ارسال پیامک استفاده خواهد شد. چنانچه پیامک با موفقیت به سرویس دهنده (اپراتور) ارسال شود، پیغام &#8220;پیامک ارسال شد&#8221; و در غیر اینصورت سایر پیغام‌های مناسب وضعیت رخ داده نمایش داده خواهد شد.<br />
برای حالت عدم ارسال پیامک، ۴ حالت مختلف تعریف شده که با توجه به وضعیت دریافتی از SmsManager، نوع خطا را به کاربر اعلام می‌کند. خطای دریافتی می‌تواند یک خطای عمومی بوده یا مربوط به در دسترس نبودن اپراتور، در دسترس نبودن سیم کارت و یا پروتکل PDU (Protocol Data Unit) باشد.<br />
برودکست دوم نیز برای شنود وضعیت تحویل یا عدم تحویل SMS به گیرنده بکار می‌رود.<br />
بعد از برودکست‌ها دو PendingIntent به صورت زیر تعریف می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
PendingIntent sentSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_SENT), 0);
PendingIntent deliverSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_DELIVERED), 0);
</pre>
<p>قبلا در جلسه <a href="http://android-studio.ir/advanced-android-notifications/" target="_blank" rel="noopener">آموزش ساخت نوتیفیکیشن‌های حرفه‌ای در اندروید</a> با کاربرد PendingIntent ها آشنا شدیم. با استفاده از متد getBroadcast، برودکست موردنظر در PendingIntent تعریف می‌شود.<br />
پارامتر سوم getBroadcast از جنس اینتنت بوده که شامل اکشن‌های SMS_SENT و SMS_DELIVERED می‌باشد که قبلا در برودکست‌ها تعیین کرده بودیم. همانطور که از نحوه نامگذاری PendingIntent ها پیداست، مورد اول برای اجرای برودکست ارسال پیامک و مورد دوم برای اجرای برودکست تحویل پیامک استفاده خواهد شد.<br />
در انتها لازم است این دو PendingIntent در smsManager جایگزین مقدارهای null شوند:</p>
<pre class="brush: java; title: ; notranslate">
smsManager.sendTextMessage(&quot;+989158888888&quot;, null, &quot;تست ارسال پیامک&quot;, sentSMS, deliverSMS);
</pre>
<p>بنابراین با ارسال پیامک توسط SmsManager در آینده و هر زمانی که گزارش مربوط به وضعیت ارسال و همچنین تحویل پیامک توسط سیستم عامل منتشر شود، توسط BroadcastReceiver ها دریافت شده و پیغام مناسب روی صفحه ظاهر خواهد شد.<br />
در نهایت، متن پیغام Toast که بعد از smsManager قرار گرفته را از &#8220;پیامک ارسال شد&#8221; به &#8220;ارسال پیامک آغاز شد&#8221; تغییر می‌دهم تا از پیغامی که در برودکست ارسال پیامک تعریف شده قابل تشخیص باشد.<br />
مجدد پروژه را اجرا کرده و روی دکمه ارسال پیامک کلیک می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sms_is_sending.png" alt="ارسال SMS توسط SmsManager در برنامه نویسی اندروید" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sms_sent_report_using_sentIntent.png" alt="گزارش ارسال پیامک توسط sentIntent" /><figcaption>گزارش ارسال پیامک توسط sentIntent</figcaption></figure>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sms_delivery_report_using_deliveryIntent.png" alt="گزارش تحویل پیامک توسط deliveryIntent" /><figcaption>گزارش تحویل پیامک توسط deliveryIntent</figcaption></figure>
<p>مشاهده می‌کنید گزارش مربوط به ارسال و تحویل پیامک توسط برنامه دریافت و پیغام آن نمایش داده شد.<br />
کد نهایی اکتیویتی تا اینجای کار:</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> در صورت نیاز به کپی کدها بهتر است از فایل سورس کد ضمیمه آموزش استفاده کنید.
</div>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.sendsms;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    Button sendBtn;
    private final int SMS_REQUEST_CODE = 100;
    String SMS_SENT = &quot;SMS_SENT&quot;;
    String SMS_DELIVERED = &quot;SMS_DELIVERED&quot;;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendBtn = findViewById(R.id.sms_btn);

        sendBtn.setOnClickListener(view -&gt; {

            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {

                requestSendSMSpermission();

            } else {

                sendMessage();

            }

        });

    }

    private void sendMessage() {

        try {

            //Broadcast for Sent SMS
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {

                    String state = &quot;&quot;;
                    switch (getResultCode()) {
                        case Activity.RESULT_OK:
                            state = &quot;پیامک ارسال شد&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                            state = &quot;یک خطای عمومی رخ داد&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_NO_SERVICE:
                            state = &quot;اپراتور در دسترس نیست&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_NULL_PDU:
                            state = &quot; پروتکل PDU در دسترس نیست&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_RADIO_OFF:
                            state = &quot;سیم کارت در دسترس نیست&quot;;
                            break;
                    }
                    Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
                }
            }, new IntentFilter(SMS_SENT));

            //Broadcast for Delivered SMS
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    String state = &quot;&quot;;
                    switch (getResultCode()) {
                        case Activity.RESULT_OK:
                            state = &quot;پیامک تحویل داده شد&quot;;
                            break;
                        case Activity.RESULT_CANCELED:
                            state = &quot;پیامک تحویل داده نشد&quot;;
                            break;
                    }
                    Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
                }
            }, new IntentFilter(SMS_DELIVERED));

            PendingIntent sentSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_SENT), 0);
            PendingIntent deliverSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_DELIVERED), 0);

            SmsManager smsManager = SmsManager.getDefault();
            smsManager.sendTextMessage(&quot;+989158888888&quot;, null, &quot; تست ارسال پیامک&quot;, sentSMS, deliverSMS);

            Toast.makeText(MainActivity.this, &quot; ارسال پیامک آغاز شد&quot;, Toast.LENGTH_SHORT).show();

        } catch (Exception e) {

            Toast.makeText(MainActivity.this, &quot; پیامک ارسال نشد&quot;, Toast.LENGTH_SHORT).show();

        }

    }

    private void requestSendSMSpermission() {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.SEND_SMS)) {

            new AlertDialog.Builder(this)
                    .setTitle(&quot;درخواست مجوز&quot;)
                    .setMessage(&quot;برای عملکرد صحیح برنامه باید دسترسی به ارسال پیامک تایید شود&quot;)
                    .setPositiveButton(&quot;موافقم&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            reqPermission();

                        }
                    })
                    .setNegativeButton(&quot;لغو&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            dialogInterface.dismiss();

                        }
                    })
                    .create()
                    .show();

        } else {

            reqPermission();

        }

    }

    private void reqPermission() {

        ActivityCompat.requestPermissions(MainActivity.this, new String&#x5B;] {Manifest.permission.SEND_SMS}, SMS_REQUEST_CODE);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {

        if (requestCode == SMS_REQUEST_CODE) {

            if (grantResults.length &gt; 0 &amp;&amp; grantResults&#x5B;0] == PackageManager.PERMISSION_GRANTED) {

                sendMessage();

            } else {

                Toast.makeText(this, &quot; مجوز رد شد&quot;, Toast.LENGTH_SHORT).show();

            }

        }

    }

}
</pre>
<h3 id="check-sim-state-before-sending-sms">بررسی وضعیت سیم کارت قبل از ارسال پیامک</h3>
<p>در قسمت قبل پس از ارسال پیامک و توسط کلاس SmsManager وضعیت سیم کارت بررسی می‌شد و در صورت عدم امکان ارسال SMS علت عدم ارسال توسط BroadcastReceiver مشخص می‌شد.<br />
اما با استفاده از کلاس TelephonyManager و متد getSimState می‌توانیم قبل از اقدام به ارسال SMS هم وضعیت سیم کارت را از سیستم عامل دریافت کرده و بر اساس آن عملیاتی انجام داد. برای مثال تا زمانی که سیم کارت در دسترس نباشد، دکمه ارسال پیامک در حالت غیر فعال (Disable) قرار گیرد و یا سایر روش‌های آگاه سازی کاربر به در دسترس نبودن سیم کارت که موضوع این جلسه نیست.<br />
البته کاربرد getSimState محدود به قبل از ارسال پیامک نمی‌شود. برنامه ما ممکن است به هر دلیلی نیاز به فعال بودن سیم کارت داشته باشد؛ مانند دریافت رمز یکبار مصرف. بنابراین می‌توانیم کاربر را از وضعیت سیم کارت دستگاه مطلع کنیم.<br />
یک متد با نام دلخواه simState درون اکتیویتی تعریف و به صورت زیر تکمیل می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
public void simState() {
    TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    int SIM_STATE = telephonyManager.getSimState();

    if (SIM_STATE == TelephonyManager.SIM_STATE_READY) {
        Toast.makeText(this, &quot;سیم کارت در دسترس است&quot;, Toast.LENGTH_SHORT).show();
    }
    else {
        String state = &quot;&quot;;
        switch (SIM_STATE) {
            case TelephonyManager.SIM_STATE_ABSENT:
                state = &quot;Sim absent&quot;;
                break;
            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
                state = &quot;Network locked&quot;;
                break;
            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
                state = &quot;Pin required&quot;;
                break;
            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
                state = &quot;PUK required&quot;;
                break;
            case TelephonyManager.SIM_STATE_UNKNOWN:
                state = &quot;Unknown&quot;;
                break;
        }
        Toast.makeText(this, state, Toast.LENGTH_SHORT).show();
    }
}
</pre>
<p>در کد فوق با استفاده از متد getSimState وضعیت سیم کارت بررسی می‌شود. چنانچه سیم کارت در دسترس باشد حالت SIM_STATE_READY برگردانده می‌شود. در غیر اینصورت یکی از ۵ حالت تعریف شده در قسمت دوم شرط برگردانده شده و پیغام مربوط به آن حالت در Toast نمایش داده خواهد شد.<br />
برای اجرای متد <span dir="ltr">simState()</span> کافیست آنرا در متد <span dir="ltr">onCreate()</span> اکتیویتی فراخوانی کنیم.<br />
پروژه را یکبار در حالتی که سیم کارت درون شیار دستگاه قرار دارد و بار دوم در حالتی که سیم کارت موجود نیست اجرا می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/get_sim_state_using_TelephonyManager_getSimState.png" alt="دریافت وضعیت سیم کارت توسط متد getSimState کلاس TelephonyManager" /><figcaption>دریافت وضعیت سیم کارت توسط متد getSimState کلاس TelephonyManager </figcaption></figure>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/get_sim_state_using_TelephonyManager_getSimState_2.png" alt="دریافت وضعیت سیم کارت توسط متد getSimState کلاس TelephonyManager در اندروید" /></p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.sendsms;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    Button sendBtn;
    private final int SMS_REQUEST_CODE = 100;
    String SMS_SENT = &quot;SMS_SENT&quot;;
    String SMS_DELIVERED = &quot;SMS_DELIVERED&quot;;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

	simState();

        sendBtn = findViewById(R.id.sms_btn);

        sendBtn.setOnClickListener(view -&gt; {

            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {

                requestSendSMSpermission();

            } else {

                sendMessage();

            }

        });

    }

    private void sendMessage() {

        try {

            //Broadcast for Sent SMS
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {

                    String state = &quot;&quot;;
                    switch (getResultCode()) {
                        case Activity.RESULT_OK:
                            state = &quot;پیامک ارسال شد&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                            state = &quot;یک خطای عمومی رخ داد&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_NO_SERVICE:
                            state = &quot;اپراتور در دسترس نیست&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_NULL_PDU:
                            state = &quot; پروتکل PDU در دسترس نیست&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_RADIO_OFF:
                            state = &quot;سیم کارت در دسترس نیست&quot;;
                            break;
                    }
                    Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
                }
            }, new IntentFilter(SMS_SENT));

            //Broadcast for Delivered SMS
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    String state = &quot;&quot;;
                    switch (getResultCode()) {
                        case Activity.RESULT_OK:
                            state = &quot;پیامک تحویل داده شد&quot;;
                            break;
                        case Activity.RESULT_CANCELED:
                            state = &quot;پیامک تحویل داده نشد&quot;;
                            break;
                    }
                    Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
                }
            }, new IntentFilter(SMS_DELIVERED));

            PendingIntent sentSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_SENT), 0);
            PendingIntent deliverSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_DELIVERED), 0);

            SmsManager smsManager = SmsManager.getDefault();
            smsManager.sendTextMessage(&quot;+989158888888&quot;, null, &quot; تست ارسال پیامک&quot;, sentSMS, deliverSMS);

            Toast.makeText(MainActivity.this, &quot; ارسال پیامک آغاز شد&quot;, Toast.LENGTH_SHORT).show();

        } catch (Exception e) {

            Toast.makeText(MainActivity.this, &quot; پیامک ارسال نشد&quot;, Toast.LENGTH_SHORT).show();

        }

    }

    private void requestSendSMSpermission() {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.SEND_SMS)) {

            new AlertDialog.Builder(this)
                    .setTitle(&quot;درخواست مجوز&quot;)
                    .setMessage(&quot;برای عملکرد صحیح برنامه باید دسترسی به ارسال پیامک تایید شود&quot;)
                    .setPositiveButton(&quot;موافقم&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            reqPermission();

                        }
                    })
                    .setNegativeButton(&quot;لغو&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            dialogInterface.dismiss();

                        }
                    })
                    .create()
                    .show();

        } else {

            reqPermission();

        }

    }

    private void reqPermission() {

        ActivityCompat.requestPermissions(MainActivity.this, new String&#x5B;] {Manifest.permission.SEND_SMS}, SMS_REQUEST_CODE);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {

        if (requestCode == SMS_REQUEST_CODE) {

            if (grantResults.length &gt; 0 &amp;&amp; grantResults&#x5B;0] == PackageManager.PERMISSION_GRANTED) {

                sendMessage();

            } else {

                Toast.makeText(this, &quot; مجوز رد شد&quot;, Toast.LENGTH_SHORT).show();

            }

        }

    }

    public void simState() {
    TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    int SIM_STATE = telephonyManager.getSimState();

    if (SIM_STATE == TelephonyManager.SIM_STATE_READY) {
        Toast.makeText(this, &quot;سیم کارت در دسترس است&quot;, Toast.LENGTH_SHORT).show();
    }
    else {
        String state = &quot;&quot;;
        switch (SIM_STATE) {
            case TelephonyManager.SIM_STATE_ABSENT:
                state = &quot;Sim absent&quot;;
                break;
            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
                state = &quot;Network locked&quot;;
                break;
            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
                state = &quot;Pin required&quot;;
                break;
            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
                state = &quot;PUK required&quot;;
                break;
            case TelephonyManager.SIM_STATE_UNKNOWN:
                state = &quot;Unknown&quot;;
                break;

            }
        	Toast.makeText(this, state, Toast.LENGTH_SHORT).show();
        }
    }

}
</pre>
<p>در این جلسه ارسال پیامک (SMS) در برنامه نویسی اندروید را به همراه نحوه‌ی دریافت گزارش ارسال و تحویل و همچنین وضعیت سیم کارت دیوایس اندرویدی بررسی کردیم. امیدوارم مفید واقع شود.<br />
موفق و پیروز باشید.</p>
<h3>مطالعه‌ی بیشتر:</h3>
<p style="text-align: left; direction: ltr;">
<a href="https://developer.android.com/reference/android/telephony/SmsManager" target="_blank" rel="noopener">https://developer.android.com/reference/android/telephony/SmsManager</a><br />
<a href="https://developer.android.com/reference/android/app/PendingIntent" target="_blank" rel="noopener">https://developer.android.com/reference/android/app/PendingIntent</a>
</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۲۸<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/14_1_Send_SMS.zip" class="button green edd-submit">دانلود رایگان با حجم ۲ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/14_1_Send_SMS.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/send-sms-in-android-programming/">ارسال پیامک (SMS) در برنامه اندرویدی + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/send-sms-in-android-programming/feed/</wfw:commentRss>
			<slash:comments>16</slash:comments>
		
		
			</item>
		<item>
		<title>طراحی صفحات با ConstraintLayout + سورس پروژه</title>
		<link>https://android-studio.ir/constraintlayout/</link>
					<comments>https://android-studio.ir/constraintlayout/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Wed, 03 Mar 2021 20:38:10 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=195516</guid>

					<description><![CDATA[<p>در جلسه قبل با عنوان آموزش طراحی رابط کاربری در اندروید با کلیات رابط کاربری، ویو، ویجت‌ها و همچنین لایه‌ (Layout یا ViewGroup) های پرکاربرد LinearLayout و RelativeLayout آشنا شدیم. در این جلسه قصد داریم طراحی صفحات با ConstraintLayout را بررسی کنیم. این layout که نسبت به سایر layout های اندروید عمر کمتری دارد، از [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/constraintlayout/">طراحی صفحات با ConstraintLayout + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/android_constraintlayout_tutorial.png" alt="آموزش طراحی رابط کاربری اندروید توسط ConstraintLayout" /><br />
در جلسه قبل با عنوان آموزش طراحی رابط کاربری در اندروید با کلیات رابط کاربری، ویو، ویجت‌ها و همچنین لایه‌ (Layout یا ViewGroup) های پرکاربرد LinearLayout و RelativeLayout آشنا شدیم. در این جلسه قصد داریم طراحی صفحات با ConstraintLayout را بررسی کنیم.<br />
این layout که نسبت به سایر layout های اندروید عمر کمتری دارد، از انعطاف بسیار بالاتری برخوردار بوده و می‌تواند زمان طراحی و حجم کدهای xml رابط کاربری اکتیویتی را تا حد زیادی کاهش دهد. در نتیجه با کاهش حجم کدها، سرعت نمایش صفحات افزایش یافته و می‌توانیم شاهد بهبود عملکرد کلی اپلیکیشن باشیم.<br />
در صورتی که با ویژگی‌ها و امکانات  به خوبی آشنا بوده و به اندازه کافی روی این ViewGroup جدید تمرینات مختلف انجام داده باشیم، احتمالا در اکثر موارد می‌توانیم بدون بکارگیری لایه‌های LinearLayout، RelativeLayout، FrameLayout، <a href="http://android-studio.ir/android-tablelayout/">TableLayout</a> و <a href="http://android-studio.ir/android-gridlayout-viewgroup/">GridLayout</a> رابط‌های کاربری مدنظر خود را در بهینه‌ترین حالت ممکن پیاده سازی کنیم.<br />
در این جلسه سعی می‌کنم تمامی قابلیت‌ها و جزئیات این layout تازه نفس را در قالب مثال بیان کنم. توجه داشته باشید این آموزش به نوعی ادامه‌ی جلسه <a href="http://android-studio.ir/android-user-interface/">آموزش طراحی رابط کاربری در اندروید</a> بوده و پیش نیاز این جلسه، آشنایی با مفاهیم ابتدایی رابط کاربری می‌باشد که قبلا به آن‌ها پرداخته‌ایم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#what-is-constraintlayout">ConstraintLayout چیست؟</a></li>
<li><a href="#design-android-ui-by-ConstraintLayout">نحوه طراحی صفحات رابط کاربری اندروید با ConstraintLayout</a>
<ul>
<li><a href="#constraints">خطوط اتصال و گره‌ها در ConstraintLayout</a></li>
<li><a href="#bias">تعیین فاصله از چپ و پایین توسط Bias</a></li>
<li><a href="#width-height">تعیین عرض و ارتفاع Widget یا View ها</a></li>
<li><a href="#clear-all">حذف یکباره تمامی Constraint ها</a></li>
<li><a href="#convert-to-constraintlayout">تبدیل ViewGroup های دیگر به ConstraintLayout</a></li>
<li><a href="#auto-constraint">ایجاد خودکار Constraint ها</a></li>
<li><a href="#margin">تعیین فاصله یا Margin پیش فرض</a></li>
<li><a href="#baseline">همتراز کردن ویجت‌های متنی توسط Baseline</a></li>
<li>همتراز کردن ویجت‌ها در جهت افقی یا عمودی</li>
<li>ایجاد فاصله یکسان بین View ها با استفاده از Chain</li>
<li>پیاده سازی استایل‌های مختلف برای Chain</li>
<li>کاربرد گزینه Autoconnection to Parent</li>
<li>کاربرد گزینه‌های Pack و Expand</li>
<li>اتصال خودکار ویجت‌ها توسط Distribute</li>
<li>کاربرد گزینه‌های Edges</li>
<li>تنظیم خودکار Baseline ها</li>
<li>تراز کردن View ها توسط Guideline یا خط راهنما</li>
<li>تعیین محدوده متغیر برای چند ویجت توسط Barrier</li>
<li>نمایش، حذف یا مخفی کردن چندین View توسط Group</li>
<li>تغییر چیدمان ویجت‌ها بر اساس اندازه صفحه نمایش توسط Flow</li>
<li>جابجایی چند ویجت به صورت گروهی توسط Layer</li>
</ul>
</li>
</ul>
</div>
<p>&nbsp;</p>
<p class="copy">این جلسه در قالب PDF و در ۱۰۷ صفحه تهیه شده که در ادامه چند صفحه‌ ابتدایی را مشاهده می‌کنید:</p>
<p></p>
<h2 id="what-is-constraintlayout">ConstraintLayout چیست؟</h2>
<p>به نام خدا. ConstraintLayout برای اولین بار در کنفرانس Google I/O در سال ۲۰۱۶ معرفی شد. اصلی ترین هدف گوگل از معرفی این Layout جدید، امکان طراحی لایه‌های پیچیده و در عین حال flat (تخت) بود؛ بر خلاف Layout های قبلی که برای طراحی یک صفحه ناگزیر به استفاده از لایه‌های تو در تو و سلسله مراتبی بودیم.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/android_constraintlayout.png" alt="برتری‌های ConstraintLayout نسبت به ViewGroup های قبلی" /></p>
<p>این یعنی دیگر لازم نیست چندین لایه RelativeLayout و LinearLayout و&#8230; درون یکدیگر و به صورت تو در تو تعریف شود تا بتوان یک صفحه چند قسمتی را ساخت. بنابراین علاوه بر سادگی پیاده سازی این لایه و کاهش حجم نهایی کدهای آن، در طراحی صفحات با ConstraintLayout کارایی و performance اکتیویتی بهبود یافته و می‌تواند در افزایش سرعت نمایش UI روی دستگاه کاربر تاثیر مثبت بگذارد.<br />
ConstraintLayout از اندروید استودیو ۲٫۲ اضافه شد و نسخه ابتدایی آن Beta 1 نام داشت. در طی این چند سال مرتبا اصلاحاتی روی آن انجام و همچنین قابلیت‌های جدیدی نیز اضافه شده است. خبر خوب اینکه این لایه از API 9 به بعد پشتیبانی می‌شود بنابراین مطمئن خواهیم بود تمامی دستگاه‌های اندرویدی که در حال حاضر در اختیار افراد هست به خوبی می‌توانند صفحاتی که با این layout طراحی شده‌اند را نمایش دهند.<br />
در ConstraintLayout می‌توانیم وضعیت قرارگیری تمامی view/widget ها را نسبت به یکدیگر و یا parent شان تعیین کنیم. برای مثال می‌توانیم یک TextView را به نحوی در زیر یک ImageView متصل کنیم که در جهت افقی، متن همواره در مرکز تصویر قرار گیرد. یا اینکه یک یا چند view بر اساس سایز صفحه نمایش دستگاه، چه از جهت افقی و چه عمودی همواره در مرکز قرار گرفته و علاوه بر آن، فاصله آنها از یکدیگر نیز درصدی ثابت باشد.<br />
این یعنی یک انعطاف پذیری بسیار بالا که تا حدود زیادی مشابه طراحی صفحات واکنشگرا یا Responsive در صفحات وب است.<br />
فکر می‌کنم هرچه اینجا بیشتر توضیح بدهم بیشتر هم شما را گیج می‌کنم! بنابراین بهتر است سراغ محیط توسعه اندروید استودیو رفته و امکانات این لایه قدرتمند را در عمل بررسی کنیم.<br />
توجه داشته باشید در نسخه‌های ابتدایی اندروید استودیو که ConstraintLayout اضافه شده بود می‌بایست ابزار آن به صورت جداگانه در SDK و در زیرمجموعه Support Repository دریافت و نصب می‌شد اما در نسخه‌های اخیر، این گزینه از لیست SDK Tools حذف شده و با سایر ابزار اصلی توسعه اندروید ادغام شده است.<br />
قبل از هرچیز توجه داشته باشید در برخورد اول قطعا کار با این layout مقداری گیج کننده خواهد بود بنابراین لازم است بارها و بارها تمرین کرده و خروجی کار را مقایسه کنید تا کاربرد هر قابلیت را بتوانید به درستی درک نمائید.</p>
<h2 id="design-android-ui-by-ConstraintLayout">نحوه طراحی صفحات رابط کاربری اندروید با ConstraintLayout</h2>
<p>قبل از هرچیز ابتدا طبق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام ConstraintLayout می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.<br />
کتابخانه این layout به صورت پیش فرض در بلاک dependencies فایل build.gradle(app) پروژه اندرویدی قرار دارد:</p>
<pre class="brush: java; title: ; notranslate">
dependencies {
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
}
</pre>
<p>در زمان نگارش این آموزش، نسخه ۲٫۰٫۴ جدیدترین نسخه از این ViewGroup است.<br />
Layout پیش فرض اکتیویتی پروژه از نوع ConstraintLayout بوده که در قسمت Component tree قابل مشاهده است:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/default_constraintlayout.png" alt="ConstraintLayout پیش فرض در اکتیویتی" /><figcaption>ConstraintLayout پیش فرض در اکتیویتی</figcaption></figure>
<p>محیط پیش نمایش در دو حالت Design و Blueprint قابل نمایش است که به منظور خلوت بودن صفحه در حین آموزش تنها از حالت Design استفاده می‌کنم.</p>
<h3 id="constraints">خطوط اتصال و گره‌ها در ConstraintLayout</h3>
<p>از ساده ترین مثال شروع می‌کنم. ملاحظه می‌کنید یک TextView به طور پیش فرض در مرکز صفحه پیش نمایش با عنوان Hello World! قرار گرفته. موس را روی TextView قرار می‌دهم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/show_constraints_of_views.png" alt="خطوط اتصال Constraint" /><figcaption>خطوط اتصال Constraint</figcaption></figure>
<p>این TextView توسط ۴ خط اتصال به ۴ لبه‌ی صفحه متصل شده است. حالا روی TextView کلیک می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/show_constraints_nodes.png" alt="نقاط اتصال در ConstraintLayout" /><figcaption>نقاط اتصال (گره یا نود) در ConstraintLayout</figcaption></figure>
<p>ملاحظه می‌کنید خطوط اتصال پررنگ شده و نقاط اتصال خطوط به ویجت مدنظر نیز به شکل دایره‌های توپر نمایش داده می‌شود. هر کدام از این دایره‌ها را یک گره یا node می‌نامیم. علت قرار گرفتن این TextView در مرکز صفحه این است که هر ۴ گره آن به چهار جهت صفحه نمایش متصل شده.<br />
قبل از انجام هر کاری کد xml صفحه را بررسی می‌کنیم:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;TextView
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;Hello World!&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>ملاحظه می‌کنید یک TextView با تعدادی ویژگی درون layout از جنس ConstraintLayout قرار گرفته است. به عبارتی این layout والد یا parent ویجت TextView است.</p>
<pre class="brush: xml; title: ; notranslate">
app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
app:layout_constraintRight_toRightOf=&quot;parent&quot;
app:layout_constraintTop_toTopOf=&quot;parent&quot;
</pre>
<p>این چهار attribute موقعیت مکانی TextView را نسبت به والد خود تعیین می‌کند. یعنی به ازاء هر خط اتصالی که از گره‌های ویجت به یکی از کناره‌های صفحه نمایش کشیده شده یکی از این ویژگی‌ها به کد TextView اضافه شده است.</p>
<pre class="brush: xml; title: ; notranslate">
app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
</pre>
<p>اگر به نحوه نامگذاری هر کدام دقت کنید کاربرد آن را متوجه خواهید شد.<br />
Bottom to Bottom of parent یعنی پایینِ TextView به پایینِ parent متصل شود. و به همین ترتیب برای ۳ مورد دیگر. نتیجه این می‌شود که ویجت از بالا و پایین به یک اندازه و همچنین از چپ و راست هم به یک اندازه فاصله گرفته بنابراین در مرکز صفحه قرار می‌گیرد.<br />
با توجه به اینکه در اینجا برای تعیین فاصله از واحدهای اندازه گیری استفاده نشده لذا یک رابط کاربری ساده و انعطاف پذیر داریم که در هر صفحه نمایشی اجرا شود اعم از کوچک و بزرگ یا افقی و عمودی، همواره این TextView در مرکز صفحه قرار خواهد گرفت.<br />
اگر یکی از این خطوط حذف شود چه اتفاقی خواهد افتاد؟ می‌خواهم خط بالا را حذف کنم. برای اینکار هم می‌توان در حالت کد، ویژگی layout_constraintTop_toTopOf را از تکست ویو حذف کرد هم اینکه در حالت دیزاین، خط بالا را انتخاب کرده و delete ‌کنیم. همچنین اگر روی هریک از خطوط راست کلیک کنیم گزینه‌های مختلفی نمایش داده می‌شود که یکی از آنها Delete است:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/delete_constraints.png" alt="حذف خطوط اتصال در ConstraintLayout" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/delete_constraints_2.png" alt="حذف خطوط اتصال در ConstraintLayout" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraint_deleted.png" alt="حذف خطوط اتصال در ConstraintLayout" /><figcaption>حذف خطوط اتصال در ConstraintLayout</figcaption></figure>
<p>مشاهده می‌کنید با حذف خط اتصال بالا، TextView از جهت افقی در مرکز قرار دارد اما در جهت عمودی به پایین صفحه چسبیده است. برای در مرکز قرار گرفتن ویجت کافی است دوباره ویژگی layout_constraintTop_toTopOf با مقدار parent را در ویرایشگر کد اضافه کرده و یا در محیط ویژوال به وسیله عمل Drag &#038; Drop دایره یا گره بالای ویجت را گرفته و روی لبه بالای صفحه رها کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_constraints_to_widgets_in_anroid_studio.png" alt="ساخت خطوط اتصال در ConstraintLayout" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_constraints_to_widgets_2.png" alt="اتصال view ها به یکدیگر در ConstraintLayout" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_constraints_to_widgets_3.png" alt="ایجاد خطوط اتصال در ConstraintLayout" /><figcaption>ایجاد خطوط اتصال در ConstraintLayout</figcaption></figure>
<p>در نهایت مجدد توانستم ویجت را نسبت به بالا و پایین در مرکز قرار دهم.<br />
برای مشاهده جزئیات بیشتر روی گزینه Attributes نوار سمت راست محیط دیزاین کلیک می‌کنم. سپس روی صفحه پیش نمایش یا Component tree ویجت TextView را انتخاب می‌کنم تا ویژگی‌های مرتبط با آن نمایش داده شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/design_attributes_section.png" alt="بخش Attributes در محیط طراحی اندروید استودیو" /><figcaption>بخش Attributes در محیط طراحی اندروید استودیو</figcaption></figure>
<p>در قسمت Declared Attributes عینا مواردی که در کد TextView دیدیم لیست شده و می‌توان هرکدام را تغییر داد.<br />
در قسمت Layout امکانات بیشتری برای اعمال تغییرات وجود دارد. در چهار جهت آیکون مربعی شکل که نماد ویجت انتخاب شده یعنی TextView است، فیلدهای عددی دیده می‌شود که در حالت پیش فرض عدد صفر را نشان می‌دهند. این اعداد، فاصله یا margin را از جهت‌هایی که توسط خط اتصال به والد یا یک ویجت دیگر متصل شده تعیین می‌کنند.<br />
در اینجا که ویجت ما در مرکز صفحه قرار گرفته تعیین فاصله اهمیتی ندارد اما در ادامه آموزش و هنگامی که بخواهیم یک ویجت را در کنار یک ویجت دیگر قرار دهیم از این ویژگی استفاده خواهیم کرد.</p>
<h3 id="bias">تعیین فاصله از چپ و پایین توسط Bias</h3>
<p>در سمت چپ و پایین دو گزینه در کنار عدد مارجین قرار دارد که توسط آن و با حرکت دادن دایره روی خط، میزان فاصله از چهار جهت را می‌توان تغییر داد. این قابلیت Bias نام دارد. دقت داشته باشید در اینجا خبری از واحدهای px و dp نیست بلکه بر اساس درصد تعیین می‌شود. به صورت پیش فرض هردو Bias روی ۵۰ قرار دارد یعنی مرکز صفحه.<br />
اگر بخاطر داشته باشید در کد مربوط به TextView ویژگی با نام Bias وجود نداشت. این یعنی مقدار پیش فرض Bias برای یک ویجت همین ۵۰% یا مقدار ۰٫۵ است و صرفا در صورتی این ویژگی به ویجت اضافه خواهد شد که مقداری غیر از ۵۰ برای آن درنظر بگیریم.<br />
برای تمرین، ویجت روی صفحه یا همین گزینه پایین که مربوط به جهت افقی هست را مقداری به سمت چپ بکشید:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_bias_attribute.png" alt="جابجایی عناصر توسط ویژگی Bias در ConstraintLayout" /><figcaption>جابجایی عناصر توسط ویژگی Bias در ConstraintLayout</figcaption></figure>
<p>ملاحظه می‌کنید عدد Horizontal Bias به ۲۵ تغییر کرد و ویجت هم در جهت افق به اندازه ۲۵ درصد به سمت چپ نزدیکتر شد. حالا اگر کد ویجت را دوباره بررسی کنید خط زیر به آن اضافه شده:</p>
<pre class="brush: xml; title: ; notranslate">
app:layout_constraintHorizontal_bias=&quot;0.25&quot;
</pre>
<p>در محیط ویژوال عدد از ۰ تا ۱۰۰ و در کد از ۰ تا ۱ تعیین می‌شود. یعنی ۲۵ درصد برای Bias افقی (Horizontal) برابر است با مقدار ۰٫۲۵ برای ویژگی آن در کدها.<br />
صفحه پیش نمایش را در حالت افقی (Landscape) قرار می‌دهم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/bias_attribute_in_landscape.png" alt="تست ویژگی Bias در صفحه نمایش Landscape" /><figcaption>تست ویژگی Bias در صفحه نمایش Landscape</figcaption></figure>
<p>باز هم ویجت در فاصله ۲۵ درصدی از سمت چپ صفحه قرار گرفته و انعطاف پذیری ConstraintLayout را ثابت می‌کند.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> ویژگی Bias فقط برای جهتی قابل استفاده است که هردو طرف آن به والد یا ویجت‌های دیگر متصل شده باشد. به طور خلاصه این ویژگی، فاصله ویجت از مرکز در جهت افقی یا عمودی را مشخص می‌کند.
</div>
<h3 id="width-height">تعیین عرض و ارتفاع Widget یا View ها</h3>
<p>عرض و ارتفاع هر ویجتی که به layout اضافه می‌شود ابتدا wrap_content است؛ یعنی به اندازه محتوای درون خودش جا می‌گیرد. اما دو حالت دیگر هم قابل انجام است.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/change_width_&#038;_height.png" alt="تغییر عرض و ارتفاع ویو ها در ConstraintLayout" /><figcaption>تغییر عرض و ارتفاع ویو ها در ConstraintLayout</figcaption></figure>
<p>در تصویر بالا مشاهده می‌کنید با بردن نشانگر بر روی یکی از فاصله‌های افقی عبارت Wrap Content نمایش داده می‌شود. یکبار روی یکی از آنها کلیک می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/change_width_&#038;_height_to_fixed.png" alt="تعیین حالت Fixed برای اندازه ویجت‌ها" /><figcaption>تعیین حالت Fixed برای اندازه ویجت‌ها</figcaption></figure>
<p>نماد فاصله‌های افقی از دو فلش به یک خط ممتد تغییر پیدا کرد و عبارت Fixed را نمایش می‌دهد.  همچنین در قسمت Declared Attributes مقدار width از wrap_content به ۷۶dp برای من تغییر داده شد یعنی اندروید استودیو یک مقدار پیش فرض در واحد dp را درنظر گرفت. حالا من می‌توانم مقدار مدنظر خودم را بجای ۷۶ وارد کنم.<br />
در مواقعی به این حالت نیاز داریم که بخواهیم برای یک ویجت عرض یا ارتفاع مشخص و ثابتی را درنظر بگیریم و نباید اندازه آن به صفحه نمایش یا حالت قرار گیری آن بستگی داشته باشد.<br />
یک مرتبه دیگر روی نماد فاصله افقی کلیک می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/change_width_&#038;_height_to_match_constraints.png" alt="تعیین حالت Match Constraints برای اندازه ویجت‌ها" /></p>
<p>اینبار نوع فاصله گذاری به Match Constraints تغییر پیدا کرد. این حالت همان match_parents در layout هایی مانند RelativeLayout است اما در ConstraintLayout برای اینکه یک ویجت تمام عرض یا ارتفاع در دسترس را اشغال کند بجای match_parent مقدار ۰dp باید داده شود که اندروید استودیو این تغییر را به صورت خودکار برای من انجام داد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/change_width_&#038;_height_to_match_constraints_2.png" alt="تعیین حالت Match Constraints برای اندازه ویجت‌ها" /></p>
<p>با انجام این تغییر، ویجت از دو طرف چپ و راست به محلی چسبید که خطوط اتصال برقرار است. در تصویر زیر هنوز هم خطوط اتصال افقی فعال هستند اما چون فاصله‌ای بین TextView و لبه‌های صفحه پیش نمایش وجود ندارد قابل مشاهده نیست. البته از گره‌های تو پر می‌توان فعال بودن این دو را تشخیص داد.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/change_width_&#038;_height_to_match_constraints_3.png" alt="تعیین حالت Match Constraints برای اندازه ویجت‌ها" /><figcaption>تعیین حالت Match Constraints برای اندازه ویجت‌ها</figcaption></figure>
<div class="alert alert-warning">
<span class="notice">نکته:</span> مانند layout های قبل، انجام تغییرات محدود به حالت دیزاین یا کد نیست و به هر دو روش می‌توان کار را انجام داد. اما در مرحله یادگیری توصیه می‌کنم هر تغییری که در یک حالت انجام می‌دهید نتیجه آنرا در حالت دیگر هم بررسی کنید تا ببینید انجام یک کار در محیط ویژوال چه کدی را به کدهای لایه اضافه می‌کند یا بلعکس اگر یک ویژگی به یک ویجت اضافه می‌کنید چه تغییری در صفحه پیش نمایش اعمال می‌شود.<br />
در صورتی که تسلط کافی بر هردو حالت کد و دیزاین داشته باشید در هنگام ساخت و ویرایش صفحات بخصوص layout های پیچیده، کنترل بیشتری بر روی لایه‌ها و ویجت‌ها خواهید داشت.
</div>
<p>خب! حالا که تا حدودی با کلیت کار آشنا شدیم ادامه آموزش را در قالب چند مثال عملی طراحی صفحات با ConstraintLayout پیش می‌برم تا درک بهتری از امکانات آن داشته باشید.<br />
از مسیر New > Layout Resource file یک layout جدید با نام layout_two.xml به پروژه اضافه می‌کنم. هر قسمت را در یک فایل جداگانه توضیح می‌دهم تا همه کدها در سورس پروژه ضمیمه شده را در اختیار داشته باشید.<br />
یک ویجت Button را از پالت ابزار کشیده و در مرکز صفحه پیش نمایش قرار می‌دهم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_view_to_center_of_constraintlayout.png" alt="نمایش یک View در وسط صفحه نمایش در ConstraintLayout" /><figcaption>نمایش یک View در وسط صفحه نمایش در ConstraintLayout</figcaption></figure>
<p>اینجا ظاهرا مشکلی نیست و پس از رها کردن، دکمه در مرکز صفحه باقی می‌ماند. حالا می‌خواهم پروژه را اجرا کنم تا خروجی کار را روی دیوایس اندرویدی هم ببینیم. در MainActivity.java فایل layout_two را جایگزین activity_main می‌کنم تا هنگام اجرای پروژه روی دیوایس نمایش داده شود.</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.constraintlayout;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_two);
    }
}
</pre>
<p>پروژه را روی <a href="http://android-studio.ir/install-genymotion/">شبیه ساز Genymotion</a> اجرا می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_difference_between_preview_&#038;_device.png" alt="تفاوت خروجی ConstraintLayout در صفحه پیش نمایش و دستگاه اندرویدی" /><figcaption>تفاوت خروجی ConstraintLayout در صفحه پیش نمایش و دستگاه اندرویدی</figcaption></figure>
<p>برخلاف آنچه در Preview اندروید استودیو دیدیم که دکمه در مرکز طولی و عرضی صفحه نمایش قرار گرفته بود، روی دیوایس اندرویدی دکمه در گوشه صفحه قرار گرفته است. به سورس layout دقت کنید:</p>
<p><span class="filename">layout_two.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&gt;

    &lt;Button
        android:id=&quot;@+id/button2&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;Button&quot;
        tools:layout_editor_absoluteX=&quot;156dp&quot;
        tools:layout_editor_absoluteY=&quot;342dp&quot; /&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>دو ویژگی layout_editor_absoluteX و layout_editor_absoluteY فقط در صفحه پیش نمایش اندروید استودیو کاربرد دارند و تا زمانی که خطوط constraint برای ویو یا ویجت ساخته نشود موقعیت آن در صفحه تغییری نخواهد کرد. گره‌های چپ و راست دکمه را به دو طرف صفحه متصل می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_left_&#038;_right_constraints.png" alt="اضافه کردن خطوط اتصال چپ و راست در ConstraintLayout" /><figcaption>اضافه کردن خطوط اتصال چپ و راست در ConstraintLayout</figcaption></figure>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/left_&#038;_right_constraints_on_device.png" alt="خروجی Constraints در شبیه ساز اندرویدی" /></p>
<p>مشاهده می‌کنید دکمه در جهت افقی درست در مرکز صفحه قرار گرفته. ویژگی‌های layout_editor_absolute تنها زمانی فعال می‌شود که خطوط اتصال در آن جهت وجود نداشته باشد. بنابراین با اضافه شدن دو خط چپ و راست، ویژگی layout_editor_absoluteX از دکمه حذف شد و فقط layout_editor_absoluteY باقی ماند:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;Button
    android:id=&quot;@+id/button2&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:text=&quot;Button&quot;
    app:layout_constraintEnd_toEndOf=&quot;parent&quot;
    app:layout_constraintStart_toStartOf=&quot;parent&quot;
    tools:layout_editor_absoluteY=&quot;342dp&quot; /&gt;
</pre>
<p>در حال حاضر نحوه نمایش وضعیت constraint ویجت دکمه به اینصورت است:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/no_constraints_in_constraint_widget_tools.png" alt="مدیریت خطوط اتصال در ابزار Constraints Widget" /><figcaption>مدیریت خطوط اتصال در ابزار Constraints Widget</figcaption></figure>
<p>در جهت بالا و پایین نماد + وجود دارد که به وضوح نشان می‌دهد در این گره‌ها اتصالی انجام نشده است. البته اگر اینجا روی + کلیک کنیم علاوه بر ایجاد خط اتصال، یک فاصله (margin) به مقداری که قبلا در layout_editor_absoluteY تعریف شده نیز اضافه خواهد شد.<br />
به عبارت دیگر layout_marginTop جایگزین ویژگی layout_editor_absoluteY می‌شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_button_in_constraint_widget_tool.png" alt="دکمه افزودن خط اتصال در ابزار Constraints Widget" /><figcaption>دکمه افزودن خط اتصال در ابزار Constraints Widget</figcaption></figure>
<p>بنابراین بهتر است ایجاد خط اتصال به همان شیوه Drag &#038; Drop روی صفحه پیش نمایش انجام شود یا در صورت ایجاد آن از طریق دکمه + لازم است مقدار margin صفر و یا به عدد دلخواه اصلاح شود.<br />
در نوار بالای پیش نمایش چندین گزینه وجود دارد که دو مورد را در اینجا و مابقی را بعدا توضیح خواهم داد.</p>
<h3 id="clear-all">حذف یکباره تمامی Constraint ها</h3>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/clear_all_constraints.png" alt="حذف تمامی خطوط اتصال با Clear All Constraints" /><figcaption>حذف تمامی خطوط اتصال با Clear All Constraints</figcaption></figure>
<p>گزینه Clear All Constraints همانطور که از نامش پیداست تمامی اتصالاتی که درون layout بر روی ویجت‌ها اعمال شده را به یکباره حذف می‌کند. این مورد مواقعی کاربرد دارد که تعدادی تغییرات در صفحه انجام داده‌ایم و لازم است همگی اصلاح شوند. با استفاده از این گزینه همه تغییرات قبلی حذف شده و لازم نیست یک به یک constraint ها را حذف کنیم.<br />
همچنین کاربرد دیگر آن هنگامی است که یک layout که قبلا با استفاده از یک ViewGroup دیگر مانند LinearLayout طراحی شده را بخواهیم به ConstraintLayout تبدیل کنیم. </p>
<h3 id="convert-to-constraintlayout">تبدیل ViewGroup های دیگر به ConstraintLayout</h3>
<p>برای مثال قصد داریم رابط کاربری مربوط به <a href="http://android-studio.ir/android-sqlite-database/">آموزش دیتابیس در اندروید</a> که قبلا با استفاده از RelativeLayout ساخته بودیم را به ConstraintLayout تبدیل کنیم. در قسمت Component Tree روی ViewGroup راست کلیک کرده و گزینه Convert RelativeLayout to ConstraintLayout را انتخاب می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/convert_a_viewgroup_to_constraintlayout.png" alt="تبدیل یک ViewGroup به ConstraintLayout در اندروید استودیو" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/convert_a_viewgroup_to_constraintlayout_2.png" alt="تبدیل یک ViewGroup به ConstraintLayout در اندروید استودیو" /><figcaption>تبدیل یک ViewGroup به ConstraintLayout در اندروید استودیو</figcaption></figure>
<p>پس از انجام عملیات تبدیل، اندروید استودیو به صورت خودکار اتصالات Constraint را بر اساس چینش قبلی ویجت‌ها انجام می‌دهد اما خب عمدتا و بخصوص در لایه‌های پیچیده، آن چیزی که ما انتظارش را داریم اتفاق نمی‌افتد و لازم است خودمان نحوه چیدمان المان‌ها را به صورت دستی انجام دهیم.</p>
<h3 id="auto-constraint">ایجاد خودکار Constraint ها</h3>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/infer_constraints.png" alt="ایجاد خودکار خطوط اتصال توسط Infer Constraints" /><figcaption>ایجاد خودکار خطوط اتصال توسط Infer Constraints</figcaption></figure>
<p>گزینه Infer Constraints عملی متضاد با Clear All Constraints را انجام می‌دهد. واژه infer به معنی &#8220;حدس زدن&#8221; است. یعنی در یک layout از جنس ConstraintLayout که یک یا چندین ویجت بدون خطوط اتصال و مابقی قابلیت‌های آن اضافه شده، با کلیک روی این گزینه، اندروید استودیو به صورت حدسی تنظیمات و خطوط اتصال را برای آنها اعمال می‌کند اما همانطور که در قسمت قبل اشاره شد، این تنظیمات عموما رضایت بخش نبوده و در عمل کاربرد زیادی نخواهد داشت.<br />
فعلا با ویجت دکمه کاری ندارم و آنرا از صفحه حذف می‌کنم.<br />
یک گزینه دیگر با نام Default Margins در بالای صفحه Preview وجود دارد:</p>
<h3 id="margin">تعیین فاصله یا Margin پیش فرض</h3>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/default_margins_in_constraintlayout.png" alt="تعیین مقدار Defalut Margin در ConstraintLayout" /><figcaption>تعیین مقدار Defalut Margin در ConstraintLayout</figcaption></figure>
<p>در طراحی یک layout برای ایجاد فاصله بین المان‌های مختلف از ویژگی Margin استفاده می‌کنیم. معمولا فاصله‌ای که در یک layout برای المان‌های مختلف استفاده می‌شود یکسان است تا چینش عناصر در رابط کاربری منظم و یکپارچه باشد.<br />
مقدار Defalut Margin به طور پیش فرض روی عدد ۰ تنظیم شده. من این مقدار را روی ۱۶dp تنظیم می‌کنم و کار را ادامه می‌دهم.<br />
ابتدا یک ImageView به صفحه اضافه می‌کنم: </p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_imageview_to_constraintlayout.png" alt="افزودن یک ویجت به constraintlayout در اندروید استودیو" /></p>
<p>در قسمت Attributes عرض این ویجت را ۰dp یا همان Match Constraint و ارتفاع را ۲۰۰dp تعیین می‌کنم. همچنین ویژگی scaleType را بر روی centerCrop تنظیم می‌کنم تا در هر اندازه‌ای از صفحه نمایش، تصویر به طور کامل در محل ImageView قرار گرفته و فضای خالی در اطراف آن ایجاد نشود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_imageview_to_constraintlayout_2.png" alt="افزودن یک ویجت به constraintlayout در اندروید استودیو" /></p>
<p>سپس گره‌های سه جهت بالا و چپ و راست را به سه طرف والد یا به عبارتی لبه‌های صفحه نمایش متصل می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_imageview_to_constraintlayout_3.png" alt="افزودن یک ویجت به constraintlayout در اندروید استودیو" /></p>
<p>در تصویر بالا ملاحظه می‌کنید دو گره متصل شده یک فاصله ۱۶dp از لبه گرفته‌اند. قبلا این عدد را در Defalut Margin تعریف کرده بودیم. همچنین بعد از اینکه گره‌های بالا و سمت چپ را متصل کردم، به دلیل اینکه قبلا عرض تصویر به اندازه عرض صفحه پیش نمایش کشیده شده بود، گره سمت راست در داخل کادر قرار ندارد. اگر موس را بر روی خط چین سمت راست قرار داده و آنرا به سمت لبه راست صفحه بکشم خط اتصال برقرار می‌شود. یا اینکه در کادر Attributes در قسمت Constraint Widget روی نماد + سمت راست ویجت ImageView کلیک می‌کنم. اینجا فاصله ۱۶dp به طور خودکار اعمال نشد بنابراین به صورت دستی وارد می‌کنم. خروجی نهایی کار به اینصورت خواهد بود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_imageview_to_constraintlayout_4.png" alt="افزودن یک ویجت به constraintlayout در اندروید استودیو" /></p>
<p><span class="filename">layout_two.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot; &gt;

    &lt;ImageView
        android:id=&quot;@+id/imageView5&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;200dp&quot;
        android:layout_marginStart=&quot;16dp&quot;
        android:layout_marginLeft=&quot;16dp&quot;
        android:layout_marginTop=&quot;16dp&quot;
        android:layout_marginEnd=&quot;16dp&quot;
        android:layout_marginRight=&quot;16dp&quot;
        android:scaleType=&quot;centerCrop&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot;
        app:srcCompat=&quot;@drawable/img_22&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>تا اینجای کار ما یک تصویر به بالای صفحه اضافه کردیم که در هر اندازه‌ای از صفحه نمایش، از سه جهت فاصله ۱۶dp خواهد داشت.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> دوباره تاکید می‌کنم برای اینکه در طراحی با استفاده از این Layout یا ViewGroup مهارت کافی کسب کنید لازم است تمرینات زیادی انجام دهید. چند صفحه نمونه را به عنوان الگو (از سایر اپلیکیشن‌ها و سورس کدها) جلوی خود قرار داده و سعی کنید صفحه‌ای مشابه آن را بسازید.<br />
ذهنتان را محدود به کارهایی که من انجام می‌دهم نکنید. برای مثال می‌توانستم اندازه کادر تصویر در صفحه پیش نمایش را کوچک کنم تا بتوانم هرسه گره را در داخل صفحه به لبه‌ها متصل کرده و بعد عرض را match constraint تعیین کنم. یا اینکه می‌شد در محیط Code به سادگی ویژگی‌های خط اتصال constraint و margin را برای ImageView تعیین کرد بدون آنکه نیازی به این جابجایی‌ها در محیط Design داشته باشیم.<br />
شاید در نحوه تنظیم چیدمان من ایرادی وجود داشته باشد و شما بتوانید آنرا بهتر و ساده‌تر اجرا کنید. البته شاید که نه؛ حتما!
</div>
<p>در قدم بعد قصد دارم یک آیکون یا تصویر را به نحوی روی تصویر فعلی قرار دهم که مرکز آن، لبه پایینی این ImageView باشد. ابتدا یک ImageView دیگر را از پالت روی صفحه پیش نمایش کشیده و یک آیکون یا تصویر را به دلخواه انتخاب می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget.png" alt="قرار گرفتن مرکز یک ویجت روی حاشیه یک ویجت دیگر" /></p>
<p>کد این ویجت به اینصورت است:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;ImageView
    android:id=&quot;@+id/imageView6&quot;
    android:layout_width=&quot;40dp&quot;
    android:layout_height=&quot;40dp&quot;
    android:background=&quot;#DFCD2E&quot;
    android:padding=&quot;5dp&quot;
    app:srcCompat=&quot;@android:drawable/stat_sys_speakerphone&quot;
    tools:layout_editor_absoluteX=&quot;47dp&quot;
    tools:layout_editor_absoluteY=&quot;244dp&quot; /&gt;
</pre>
<p>حالا گره‌های بالا و پایین آیکون را به گره پایین ImageView قبلی متصل می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget_2.png" alt="قرار گرفتن مرکز یک ویجت روی حاضیه یک ویجت دیگر" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget_3.png" alt="قرار گرفتن مرکز یک ویجت روی حاضیه یک ویجت دیگر" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget_4.png" alt="قرار گرفتن مرکز یک ویجت روی حاضیه یک ویجت دیگر" /></p>
<p>ملاحظه می‌کنید پس از انجام این کار، مرکز افقی تصویر دوم بر روی خط پایینی تصویر اول قرار گرفت. به همین سادگی!<br />
در نهایت نود سمت چپ را هم به سمت چپ تصویر بزرگ متصل می‌کنم. با توجه به اینکه قبلا مقدار ۱۶dp را برای فاصله پیش فرض یا همان Default Margin تعیین کرده بودم، در اینجا نیز همین مقدار برای فاصله تعیین می‌شود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget_5.png" alt="قرار گرفتن مرکز یک ویجت روی حاضیه یک ویجت دیگر" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget_6.png" alt="قرار گرفتن مرکز یک ویجت روی حاضیه یک ویجت دیگر" /></p>
<p>حتما کد زیر و سایر کدهای هر قسمت از آموزش را به دقت بررسی کرده تا به خوبی درک کنید با انجام هر کار در محیط دیزاین، چه کدهایی به layout و ویجت‌ها اضافه شده است. </p>
<p><span class="filename">layout_two.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&gt;

    &lt;ImageView
        android:id=&quot;@+id/imageView5&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;200dp&quot;
        android:layout_marginStart=&quot;16dp&quot;
        android:layout_marginLeft=&quot;16dp&quot;
        android:layout_marginTop=&quot;16dp&quot;
        android:layout_marginEnd=&quot;16dp&quot;
        android:layout_marginRight=&quot;16dp&quot;
        android:scaleType=&quot;centerCrop&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot;
        app:srcCompat=&quot;@drawable/img_22&quot; /&gt;

    &lt;ImageView
        android:id=&quot;@+id/imageView6&quot;
        android:layout_width=&quot;40dp&quot;
        android:layout_height=&quot;40dp&quot;
        android:layout_marginStart=&quot;16dp&quot;
        android:layout_marginLeft=&quot;16dp&quot;
        android:background=&quot;#DFCD2E&quot;
        android:padding=&quot;5dp&quot;
        app:layout_constraintBottom_toBottomOf=&quot;@+id/imageView5&quot;
        app:layout_constraintStart_toStartOf=&quot;@+id/imageView5&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/imageView5&quot;
        app:srcCompat=&quot;@android:drawable/stat_sys_speakerphone&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>فرض کنید می‌خواهیم یک آیکون دیگر با همین وضعیت در سمت راست تصویر اضافه کنیم. تنها تفاوت در این است که در مرحله آخر بجای گره چپ، باید گره راست آیکون به گره راست تصویر بزرگ متصل شود.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> امیدوارم تا اینجای کار به خوبی درک کرده باشید که اهمیتی ندارد هنگام اضافه کردن یک ویجت از پالت، آنرا کجا رها می‌کنیم. موقعیتی که بعد از رها کردن ویجت روی صفحه پیش نمایش نشان داده می‌شود صرفا مربوط به همین محیط دیزاین اندروید استودیو است.<br />
یعنی اگر آیکونی که در اینجا قبل از اتصال گره‌ها در سمت چپ صفحه قرار داشت را بجای اتصال گره سمت چپ به قسمت چپ تصویر، گره راست آنرا به گره سمت راست تصویر یا لبه سمت راست صفحه متصل می‌کردم، در نهایت در سمت راست قرار می‌گرفت.
</div>
<h3 id="baseline">همتراز کردن ویجت‌های متنی توسط Baseline</h3>
<p>یکی دیگر از قابلیت‌های بسیار کاربردی ConstraintLayout در ویجت‌های متنی مانند EditText و Button ها استفاده می‌شود و Baseline نام دارد. توسط این ویژگی به سادگی می‌توانیم دو ویجت را در راستای افقی در یک تراز قرار دهیم.<br />
یک TextView و یک EditText به صفحه اضافه می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_baseline.png" alt="همتراز کردن Widget های متنی توسط ویژگی Baseline" /></p>
<p>قابلیت Baseline بصورت پیش فرض روی ویجت‌ها فعال نیست و با راست کلیک روی آن و انتخاب گزینه Show Baseline فعال می‌گردد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_baseline_2.png" alt="همتراز کردن Widget های متنی توسط ویژگی Baseline" /></p>
<p>پس از انجام این کار یک قسمت کپسولی شکل به وسط EditText اضافه شد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_baseline_3.png" alt="همتراز کردن Widget های متنی توسط ویژگی Baseline" /></p>
<p>با کشیدن آن به سمت وسط ویجت TextView کپسول Baseline این ویجت نیز ظاهر می‌شود. بنابراین خطی که از Baseline ویجت EditText کشیده شده را روی آن رها می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_baseline_4.png" alt="همتراز کردن Widget های متنی توسط ویژگی Baseline" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_baseline_5.png" alt="همتراز کردن Widget های متنی توسط ویژگی Baseline" /><figcaption>همتراز کردن Widget های متنی توسط ویژگی Baseline</figcaption></figure>
<p>حالا این دو ویجت در یک تراز قرار گرفته‌اند به طوری که متن درون EditText کاملا همراستای متن TextView دیده می‌شود. با تغییر موقعیت TextView ویجت EditText هم در همان جهت حرکت می‌کند و تراز باز هم برقرار باقی می‌ماند.<br />
منابع تکمیلی: <a href="https://developer.android.com/reference/androidx/constraintlayout/widget/ConstraintLayout">Developer</a> ، <a href="https://developer.android.com/reference/androidx/constraintlayout/widget/Group">Developer</a> ، <a href="https://developer.android.com/training/constraint-layout">Developer</a> ، <a href="https://developer.android.com/reference/androidx/constraintlayout/widget/Barrier">Developer</a> ، <a href="https://developer.android.com/reference/androidx/constraintlayout/helper/widget/Flow">Developer</a> ، <a href="https://medium.com/androiddevelopers/introducing-constraint-layout-2-0-9daa3e99995b">Medium</a></p>
<p class="pdfdl">جهت مطالعه ادامه آموزش، فایل PDF را دانلود نمائید</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<p class="copy">با توجه به اینکه آموزش‌های پایه با قیمت پایین در اختیار کاربر قرار گرفته و درآمد حاصل صرف تامین هزینه‌های وب سایت و تهیه آموزش‌های آتی می‌شود، به اشتراک گذاری این فایل با دیگران خلاف اخلاق است.</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه کامل این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۱۰۷<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۴ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : ۴۶ هزار تومان<br />
<span class="notice">توجه: صرفا در صورتی از درگاه پشتیبان استفاده کنید که قادر به پرداخت از طریق سبد دانلود نباشید.</span><br />
<a href="https://android-studio.ir/checkout/?edd_action=add_to_cart&#038;download_id=195541" class="button green edd-submit" rel="noopener noreferrer"><i class="titleicon fa fa-shopping-cart fa-lg"></i>افزودن به سبد دانلود</a> <a href="https://atisdesign.ir/android-files/" class="button red edd-submit" target="_blank" rel="noopener noreferrer">درگاه پشتیبان</a>
</div>
<p>نوشته <a href="https://android-studio.ir/constraintlayout/">طراحی صفحات با ConstraintLayout + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/constraintlayout/feed/</wfw:commentRss>
			<slash:comments>21</slash:comments>
		
		
			</item>
		<item>
		<title>مکان یابی در اندروید توسط GoogleApi</title>
		<link>https://android-studio.ir/android-location-using-googleapi/</link>
					<comments>https://android-studio.ir/android-location-using-googleapi/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Tue, 21 Jul 2020 14:45:51 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=185452</guid>

					<description><![CDATA[<p>قابلیت مکان یابی در ابزارهای هوشمند و بخصوص دستگاه‌های اندرویدی شامل موبایل و تبلت، به یکی از پرکاربردترین امکانات این ابزار بدل شده و در طیف گسترده‌ای از برنامه‌ها و به تناسب سرویسی که به کاربران ارائه می‌دهند، این قابلیت پیاده سازی شده است. اپلیکیشن‌های حمل و نقل شامل درخواست تاکسی و پیک، فروشگاه‌های آنلاین [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/android-location-using-googleapi/">مکان یابی در اندروید توسط GoogleApi</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/location/android_location_api.png" alt="آموزش مکان یابی GPS در برنامه نویسی اندروید توسط GoogleApi" /></p>
<p>قابلیت مکان یابی در ابزارهای هوشمند و بخصوص دستگاه‌های اندرویدی شامل موبایل و تبلت، به یکی از پرکاربردترین امکانات این ابزار بدل شده و در طیف گسترده‌ای از برنامه‌ها و به تناسب سرویسی که به کاربران ارائه می‌دهند، این قابلیت پیاده سازی شده است.<br />
اپلیکیشن‌های حمل و نقل شامل درخواست تاکسی و پیک، فروشگاه‌های آنلاین و برنامه‌های مسیریاب نمونه‌هایی از کاربرد قابلیت مکان‌یابی کاربر را نشان می‌دهد. در این برنامه‌ها، موقعیت مکانی کاربر دریافت شده و متناسب با ماهیت و کاربرد برنامه، پردازش مربوطه انجام می‌پذیرد.<br />
در این جلسه قصد دارم نحوه پیاده سازی قابلیت مکان یابی در اندروید توسط FusedLocationProviderClient را آموزش دهم که از زیرمجموعه‌های GoogleApi محسوب می‌شود.</p>
<p><b>این بخش شامل مباحث زیر می‌باشد:</b></p>
<ul>
<li>معرفی API مکان یابی گوگل با نام Fused location provider</li>
<li>تعریف مجوز دسترسی به موقعیت مکانی در مانیفست پروژه</li>
<li>استفاده از کلاس LocationRequest جهت ارسال درخواست مکان یابی در اندروید به همراه تنظیمات مدنظر</li>
<li>استفاده از LocationSettingsRequest.Builder برای بیلد کردن تنظیمات درخواست</li>
<li>دریافت پاسخ موقعیت مکانی توسط LocationCallback</li>
<li>نحوه فعال و غیر فعال کردن دکمه‌ها</li>
<li>نحوه استفاده از آیتم‌های مکان یابی در اندروید مانند GPS، WiFi و شبکه موبایل</li>
<li>بررسی همخوانی امکانات مکان یابی دستگاه با آنچه برنامه ما نیاز دارد توسط SettingsClient</li>
<li>تعیین مدت فاصله زمانی بین بروزرسانی‌های موقعیت مکانی</li>
<li>نمایش دیالوگ فعالسازی قابلیت Location در صورت خاموش بودن آن</li>
<li>توقف دریافت و بروزرسانی موقعیت مکانی توسط removeLocationUpdates</li>
<li>توقف عملیات بروزرسانی موقعیت مکانی هنگام توقف اکتیویتی جهت کاهش مصرف انرژی</li>
</ul>
<p class="copy">تصاویر نهایی پروژه:</p>
<div class="col-lg-12 demoes">
<div class="col-lg-3 col-md-6 col-sm-12">
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/location/get_latitude_longitude_and_update_it.png" alt="نمایش طول و عرض جغرافیایی در اکتیویتی" />
</div>
<div class="col-lg-3 col-md-6 col-sm-12">
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/location/start_location_update_using_FusedLocationClient_API.png" alt="مکان یابی و بروزرسانی آن توسط API گوگل در اندروید" />
</div>
<div class="col-lg-3 col-md-6 col-sm-12">
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/location/checkLocationSettings_addOnFailureListener_method.png" alt="نمایش دیالوگ فعالسازی قابلیت مکان یابی" />
</div>
<div class="col-lg-3 col-md-6 col-sm-12">
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/location/location_runtime_permission.png" alt="دریافت مجوز دسترسی به موقعیت مکانی از کاربر" />
</div>
</div>
<p>&nbsp;</p>
<p class="copy">این جلسه در قالب PDF و در ۳۴ صفحه تهیه شده که در ادامه چند صفحه‌ ابتدایی را مشاهده می‌کنید:</p>
<p></p>
<h2>قابلیت مکان یابی در اندروید</h2>
<p>برای مکان یابی در سیستم عامل اندروید نیاز به انجام کار پیچیده و نوشتن کدهای طولانی نداریم. به لطف گوگل، در Google Play services یک Location API با نام <a href="https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient" target="_blank" rel="noopener noreferrer">Fused location provider</a> در دسترس توسعه دهندگان قرار داده شده که با پیاده سازی آن در اپلیکیشن، می‌توان آخرین موقعیت مکانی کاربر را بدست آورد و از آن درون برنامه استفاده کرد.<br />
این API از داده‌های دریافتی از GPS (جی پی اس) و همچنین سایر آیتم‌هایی که برای تعیین موقعیت مکانی کاربر استفاده می‌شود مانند Wi-Fi و شبکه موبایل، اطلاعات مربوط به وضعیت فعلی دستگاه را دریافت کرده و مختصات آن را در دسترس برنامه‌هایی قرار می‌دهد که به این اطلاعات نیاز دارند. ضمن اینکه مختصات دائما بروز شده و می‌توانیم در فواصل مشخصی، مختصات جدید موقعیت دستگاه را مرتبا دریافت کنیم.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> همانطور که اشاره شد، این API به Google Play services وابسته است لذا در دستگاه‌هایی که این سرویس نصب یا فعال نباشد، امکان مکان یابی فراهم نیست. برای مثال در شبیه ساز Genymotion باید Open Gapps روی دیوایس فعال شده باشد.
</div>
<p>برای دریافت موقعیت مکانی کاربر به واسطه Google Play services لازم است از کلاس FusedLocationProviderClient در پروژه خود استفاده کنیم.<br />
در این جلسه یک پروژه ساده را ایجاد می‌کنیم که با فعال شدن قابلیت مکان یابی در اندروید، به طور مستمر و هر ۱۰ ثانیه یکبار مختصات موقعیت مکانی دستگاه را نمایش می‌دهد.</p>
<h2>پروژه نمایش موقعیت جغرافیایی در اندروید</h2>
<p>قصد دارم یک پروژه ساده را تمرین و بررسی کنیم که دو Button و سه TextView در اکتیویتی تعریف شده است. با کلیک روی دکمه نخست، قابلیت مکان یابی دستگاه اندرویدی فعال شده و مختصات دریافتی و همچنین زمان آخرین بروزرسانی در TextView ها نمایش داده می‌شود. همچنین با کلیک روی دکمه دوم، قابلیت مکان یابی غیر فعال خواهد شد.<br />
طبق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام Location می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.<br />
در ابتدای مبحث عنوان شد که ابزار مورد استفاده ما یکی از Location API های Google Play services هست. بنابراین در قدم اول لازم است کتابخانه موردنیاز این API را به پروژه اضافه کنیم:</p>
<p><span class="filename">build.gradle(app)</span></p>
<pre class="brush: java; title: ; notranslate">
apply plugin: 'com.android.application'

android {
    compileSdkVersion 30
    buildToolsVersion &quot;30.0.0&quot;

    defaultConfig {
        applicationId &quot;ir.android_studio.location&quot;
        minSdkVersion 19
        targetSdkVersion 30
        versionCode 1
        versionName &quot;1.0&quot;

        testInstrumentationRunner &quot;androidx.test.runner.AndroidJUnitRunner&quot;
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: &quot;libs&quot;, include: &#x5B;&quot;*.jar&quot;])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.gms:play-services-location:17.0.0'
}
</pre>
<p>کتابخانه com.google.android.gms:play-services-location همانطور که از نامش پیداست برای استفاده از قابلیت location در Play services ساخته شده.<br />
مسلما برنامه ما برای دریافت موقعیت مکانی، نیاز به دریافت مجوز حق دسترسی یا Permission مربوطه از سیستم عامل را دارد. قبلا در مبحث <a href="http://android-studio.ir/android-runtime-permission/">آموزش Runtime Permission در اندروید</a> با انواع مجوزها آشنا شدیم. برای دسترسی برنامه به موقعیت مکانی، مجوز زیر را درون مانیفست پروژه تعریف می‌کنم:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot;/&gt;
</pre>
<p>همچنین از آنجایی که قصد داریم از قابلیت مکان یابی در اندروید به واسطه API موجود در Google Play services دستگاه استفاده کنیم، باید مشخص شود برنامه با چه نسخه‌ای از ابزار Google Play services موجود در SDK اندروید استودیو ساخته شده است. تگ meta-data زیر را به بدنه تگ application مانیفست اضافه می‌کنیم:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;meta-data android:name=&quot;com.google.android.gms.version&quot;
    android:value=&quot;@integer/google_play_services_version&quot; /&gt;
</pre>
<p>در انتها، مانیفست پروژه به صورت زیر تکمیل می‌شود:</p>
<p><span class="filename">AndroidManifest.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.location&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot;/&gt;

    &lt;application
        android:allowBackup=&quot;true&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/AppTheme&quot;&gt;

        &lt;meta-data android:name=&quot;com.google.android.gms.version&quot;
            android:value=&quot;@integer/google_play_services_version&quot; /&gt;

        &lt;activity android:name=&quot;.MainActivity&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;

&lt;/manifest&gt;
</pre>
<p>در مرحله بعد، آیتم‌های مورد نیاز در layout اکتیویتی را اضافه می‌کنم که شامل دو Button و سه TextView است:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    android:orientation=&quot;vertical&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;Button
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;شروع مکان یابی&quot;
        android:textSize=&quot;20dp&quot;
        android:id=&quot;@+id/start_btn&quot;
        android:onClick=&quot;startUpdatesButtonHandler&quot;/&gt;

    &lt;Button
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;توقف مکان یابی&quot;
        android:textSize=&quot;20dp&quot;
        android:id=&quot;@+id/stop_btn&quot;
        android:onClick=&quot;stopUpdatesButtonHandler&quot;
        android:enabled=&quot;false&quot;/&gt;

    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/latitude_txt&quot;
        android:gravity=&quot;center&quot;
        android:textSize=&quot;20dp&quot; /&gt;

    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/longitude_txt&quot;
        android:gravity=&quot;center&quot;
        android:textSize=&quot;20dp&quot;/&gt;

    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/update_txt&quot;
        android:gravity=&quot;center&quot;
        android:textSize=&quot;20dp&quot;/&gt;

&lt;/LinearLayout&gt;
</pre>
<p>دو ویژگی جدید به نام‌های onClick و enabled در دکمه‌ها استفاده شده. ویژگی اول برای تعریف متد مربوط به رویداد دکمه در اکتیویتی است که جایگزین setOnClickListener بشمار می‌رود. مورد دوم هم چنانچه با مقدار false تعریف شود، دکمه در حالت پیش فرض به صورت غیر فعال و غیر قابل کلیک ظاهر می‌شود.<br />
کارکرد پروژه ما به اینصورت است که با کلیک روی دکمه اول، ابتدا بررسی می‌شود آیا پرمیشن Location قبلا به برنامه ما داده شده یا نه. بعد از دریافت مجوز دسترسی از کاربر، در صورتی که قابلیت مکان یابی دستگاه فعال بوده و موقعیت جغرافیایی آن در دسترس باشد، اطلاعات آن شامل Latitude (عرض جغرافیایی)، Longitude (طول جغرافیایی) و همچنین آخرین زمان دریافت آخرین موقعیت در قالب سه TextView نمایش داده می‌شود. قبلا در <a href="http://android-studio.ir/add-google-map-to-android-app/">آموزش نمایش نقشه Google Map در برنامه اندرویدی</a> با این اصطلاحات آشنا شدیم.<br />
همچنین در صورت کلیک روی دکمه دوم، عملیات بروزرسانی موقعیت مکانی متوقف خواهد شد. البته چنانچه قابلیت مکان یابی فعال نباشد ابتدا یک دیالوگ به کاربر نمایش داده می‌شود و اعلام می‌کند این برنامه به فعال شدن Location نیاز دارد که با تایید آن توسط کاربر، این قابلیت فعال خواهد شد.<br />
حالا نوبت به نوشتن کدهای back end اکتیویتی می‌رسد. در قدم اول، MainActivity پروژه را به صورت زیر تکمیل کردم که جزئیات را به مرور توضیح خواهم داد:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.location;

import androidx.appcompat.app.AppCompatActivity;

import android.location.Location;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.SettingsClient;

public class MainActivity extends AppCompatActivity {

    private Button startBtn, stopBtn;
    private TextView latitudeTxt, longitudeTxt, updateTimeTxt;

    private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 2;
    private static final int REQUEST_CHECK_SETTINGS = 1;
    private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;
    private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = 5000;
    private final static String KEY_REQUESTING_LOCATION_UPDATES = &quot;requesting-location-updates&quot;;
    private final static String KEY_LOCATION = &quot;location&quot;;
    private final static String KEY_LAST_UPDATED_TIME_STRING = &quot;last-updated-time-string&quot;;
    private Boolean requestingLocationUpdates;
    private String lastUpdateTime;

    private FusedLocationProviderClient mFusedLocationClient;
    private SettingsClient settingsClient;
    private LocationRequest locationRequest;
    private LocationSettingsRequest locationSettingsRequest;
    private LocationCallback locationCallback;
    private Location currentLocation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startBtn = findViewById(R.id.start_btn);
        stopBtn = findViewById(R.id.stop_btn);
        latitudeTxt = findViewById(R.id.latitude_txt);
        longitudeTxt = findViewById(R.id.longitude_txt);
        updateTimeTxt = findViewById(R.id.update_txt);

        requestingLocationUpdates = false;
        lastUpdateTime = &quot;&quot;;

       mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
       settingsClient = LocationServices.getSettingsClient(this);


    }
}
</pre>
<p>همانطور که ملاحظه می‌کنید ابتدا دکمه‌ و TextView ها را تعریف کرده‌ام.<br />
همچنین چندین متغیر از جنس int، long، String و Boolean و در نهایت، از ۶ کلاس که مربوط به کتابخانه play-services-location هستند یک نمونه ساخته‌ام.<br />
در ابتدا برای متغیر requestingLocationUpdates درون متد onCreate مقدار false تعریف کردم زیرا در حالت عادی و تا قبل از کلیک روی دکمه شروع مکان یابی نباید موقعیت مکانی دریافت و بروزرسانی شود. برای متغیری که جهت نمایش آخرین تاریخ بروزرسانی موقعیت تعریف شده هم یک رشته خالی به عنوان مقدار پیش فرض تعریف شده.<br />
در پایان با استفاده از دو متد getFusedLocationProviderClient و getSettingsClient دو کلاس FusedLocationProviderClient و SettingsClient آماده استفاده در اکتیویتی شده‌اند.</p>
<h3>ارسال درخواستِ دریافت موقعیت مکانی:</h3>
<p>برای ارسال درخواست دریافت موقعیت مکانی به API و همچنین تعیین فاصله زمانی بروزرسانی آن، از کلاس LocationRequest استفاده می‌کنیم. یک متد با نام دلخواه createLocationRequest به اکتیویتی اضافه کرده و داخل آن را به صورت زیر تکمیل می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
private void createLocationRequest() {

    locationRequest = new LocationRequest();
    locationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
    locationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

}
</pre>
<p>در کد فوق ابتدا از کلاس LocationRequest یک نمونه با نام locationRequest ساخته شده. سپس ۳ متد از این کلاس تعریف و مقدار دهی شده است.<br />
<b>setInterval:</b> به وسیله این متد، فاصله زمانی بروزرسانی موقعیت مکانی را تعیین می‌کنیم که بر حسب میلی ثانیه تعریف می‌شود. من برای متغیر UPDATE_INTERVAL_IN_MILLISECONDS که از جنس long تعریف شده، مقدار ۱۰۰۰۰ تعیین کردم بنابراین برنامه من هر ۱۰ ثانیه یکبار جدیدترین مختصات موقعیت مکانی را از سیستم عامل و به عبارت دیگر از Google Play services دریافت می‌کند.<br />
<b>setFastestInterval:</b>: این متد، سریع‌ترین (Fastest) فاصله زمانی برای دریافت بروزرسانی موقعیت مکانی را تعیین می‌کند. کاربرد این متد این است که چنانچه در بازه زمانی کوتاهتری از آنچه قبلا در setInterval تعریف کردیم، موقعیت مکانی دستگاه به واسطه سایر برنامه‌های موجود روی دستگاه (مانند اپلیکیشن‌های مکان یاب و&#8230;) بروزرسانی شود، مختصات توسط برنامه ما نیز دریافت شده و ارسال درخواست بروزرسانی موقعیت مکانی از جانب برنامه ما به تعویق می‌افتد. این کار باعث کاهش تعداد درخواست‌های بروزرسانی از جانب برنامه‌های نصب شده روی دستگاه شده که در نهایت موجب صرفه جویی در مصرف باتری خواهد شد.<br />
من برای این متد مقدار ۵۰۰۰ میلی ثانیه را تعیین کردم. بنابراین چنانچه بعد از آخرین درخواست بروزرسانی از جانب برنامه ما، یک درخواست بروزرسانی موقعیت مکانی در کمتر از مدت ۵ ثانیه از جانب برنامه دیگری به API ارسال شود، مختصات دریافتی توسط برنامه ما نیز دریافت شده و ارسال درخواست بعدی تا ۱۰ ثانیه دیگر به تعویق می‌افتد.<br />
<b>setPriority:</b>: این متد همانطور که از نامش پیداست (Priority به معنی اولویت) اولویت درخواست مکان یابی ما را برای سیستم عامل مشخص می‌کند. این اولویت شامل نوع و دقت مکان یابی است که مشخص می‌کند در تعیین موقعیت از چه پارامترهایی استفاده و از چه آیتم‌های صرف نظر شود.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/location/google_play_services_LocationRequest_setPriority_method.png" alt="تعیین دقت مکان یابی Google Play services توسط متد setPriority" /></p>
<p>به عنوان مثال PRIORITY_HIGH_ACCURACY بیشترین دقت را در تعیین مکان بکار می‌گیرد که عمدتا از GPS برای تعیین موقعیت استفاده می‌شود و طبیعتا مصرف باتری نیز با افزایش همراه خواهد بود. اما در نوع دیگری مانند PRIORITY_BALANCED_POWER_ACCURACY عمدتا از اطلاعات دریافتی از شبکه WiFi و دکل‌های مخابراتی استفاده می‌شود که مصرف انرژی کمتری داشته و البته دقت آن نیز نسبت به GPS به مراتب پایین تر خواهد بود.<br />
برای من دقت در اولویت است بنابراین PRIORITY_HIGH_ACCURACY را انتخاب کرده‌ام.</p>
<h3>تعیین نوع سرویس‌های مکان یابی</h3>
<p>در قسمت قبل، تنظیماتی که برای سرویس مکان یابی لازم بود را در locationRequest تعیین کردیم. برای ارسال این تنظیمات به API مکان یابی Google Play services باید از کلاس LocationSettingsRequest.Builder استفاده کنیم.<br />
یک تابع جدید با نام دلخواه buildLocationSettingsRequest در اکتیویتی ایجاد کردم که به صورت زیر تکمیل شده:</p>
<pre class="brush: java; title: ; notranslate">
private void buildLocationSettingsRequest() {

    LocationSettingsRequest.Builder lsrBuilder = new LocationSettingsRequest.Builder();
    lsrBuilder.addLocationRequest(locationRequest);
    locationSettingsRequest = lsrBuilder.build();

}
</pre>
<h3>دریافت پاسخ موقعیت مکانی</h3>
<p>درخواستی که در قسمت نخست و توسط کلاس LocationRequest به API ارسال شد، توسط کلاس LocationCallback دریافت می‌شود. یک متد دیگر با نام دلخواه createLocationCallback به اکتیویتی اضافه کرده و به صورت زیر تکمیل می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
private void createLocationCallback() {

    locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);
            
            currentLocation = locationResult.getLastLocation();
            lastUpdateTime = DateFormat.getTimeInstance().format(new Date());
            updateLocationUI();

        }
    };

}
</pre>
<p>ابتدا یک نمونه از کلاس LocationCallback ساخته‌ام. سپس متد onLocationResult را تعریف کرده‌ام:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/location/google_play_services_LocationCallback_onLocationResult_method.png" alt="دریافت موقعیت مکانی در اندروید توسط متد LocationCallback" /></p>
<p>این متد یک پارامتر از جنس LocationResult دارد که به صورت پیش فرض نام locationResult برای آن انتخاب شده است. این متد هنگامی فراخوانی می‌شود که موقعیت مکانی دستگاه در دسترس باشد.<br />
داخل این متد، سه خط زیر اضافه شده:</p>
<pre class="brush: java; title: ; notranslate">
currentLocation = locationResult.getLastLocation();
lastUpdateTime = DateFormat.getTimeInstance().format(new Date());
updateLocationUI();
</pre>
<p>currentLocation را قبلا داخل بدنه کلاس از جنس کلاس Location تعریف کردم. در اینجا آخرین موقعیت مکانی دستگاه توسط getLastLocation دریافت شده و در currentLocation ذخیره می‌شود. </p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> همواره به نامگذاری متدها و کلاس‌ها دقت کنید. در اکثر موارد، صرفا با ترجمه نام می‌توانید به کاربرد آن پی ببرید. برای مثال get last location برابر با &#8220;دریافت آخرین موقعیت مکانی&#8221; و current location برابر با &#8220;موقعیت مکانی فعلی&#8221; است. یعنی ما آخرین موقعیت را از API دریافت کرده و در متغیری ذخیره می‌کنیم تا در برنامه خود از آن به عنوان موقعیت فعلی دستگاه استفاده کنیم.
</div>
<p>در خط دوم، آخرین زمان بروزرسانی موقعیت مکانی در متغیر lastUpdateTime ذخیره می‌شود. عملیات بروزرسانی صفحه برنامه و نمایش مختصات جغرافیایی و همچنین تاریخ بروزرسانی را در یک متد مجزا انجام خواهیم داد که نام updateLocationUI را برای آن انتخاب کرده‌ام. در ادامه مبحث این متد را کامل خواهیم کرد.</p>
<h3>تکمیل متد updateLocationUI</h3>
<p>متد را به اینصورت تکمیل می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
private void updateLocationUI() {

    if (currentLocation != null) {
        latitudeTxt.setText(String.format(Locale.ENGLISH,  &quot;%s: %f&quot;, &quot;عرض جغرافیایی&quot;, currentLocation.getLatitude()));
        longitudeTxt.setText(String.format(Locale.ENGLISH,  &quot;%s: %f&quot;, &quot;طول جغرافیایی&quot;, currentLocation.getLongitude()));
        updateTimeTxt.setText(String.format(Locale.ENGLISH,  &quot;%s: %s&quot;, &quot;آخرین بروزرسانی&quot;, lastUpdateTime));
    }

}
</pre>
<p>در اینجا بررسی می‌شود چنانچه currentLocation خالی نبود و به عبارتی، مختصات جغرافیایی را از API دریافت کرده باشد، به همراه زمان آخرین بروزرسانی موقعیت روی TextView های مربوطه نمایش داده شوند.</p>
<h3>بروزرسانی داده‌های دریافتی روی صفحه</h3>
<p>برای ذخیره و بروزرسانی مختصات و زمان آخرین بروزرسانی، ابتدا متد onSaveInstanceState را درون اکتیویتی اضافه کرده و تکمیل می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/location/add_onSaveInstanceState_method_to_activity.png" alt="بروزرسانی موقعیت جغرافیایی دستگاه اندرویدی به واسطه onSaveInstanceState" /></p>
<pre class="brush: java; title: ; notranslate">
@Override
public void onSaveInstanceState(@NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) {

    outState.putBoolean(KEY_REQUESTING_LOCATION_UPDATES, requestingLocationUpdates);
    outState.putParcelable(KEY_LOCATION, currentLocation);
    outState.putString(KEY_LAST_UPDATED_TIME_STRING, lastUpdateTime);

    super.onSaveInstanceState(outState, outPersistentState);
}
</pre>
<p>در خط اول، یک مقدار از جنس Boolean یعنی true یا false در Bundle ذخیره می‌شود که مشخص می‌کند آیا درخواست بروزرسانی موقعیت ارسال شده یا خیر. همچنین در خط دوم موقعیت فعلی و در خط سوم، زمان بروزرسانی ذخیره می‌شود.<br />
حالا برای فراخوانی داده‌های ذخیره شده در قسمت قبل و ارسال آنها به رابط کاربری، یک متد دیگر با نام دلخواه updateValuesFromBundle به اکتیویتی اضافه می‌کنم. این تابع یک پارامتر از جنس Bundle دارد که نام savedState را برای آن انتخاب کرده‌ام:</p>
<pre class="brush: java; title: ; notranslate">
private void updateValuesFromBundle(Bundle savedState) {

    if (savedState != null) {
        if (savedState.containsKey(KEY_REQUESTING_LOCATION_UPDATES)) {
            requestingLocationUpdates = savedState.getBoolean(KEY_REQUESTING_LOCATION_UPDATES);
        }
        if (savedState.containsKey(KEY_LOCATION)) {
            currentLocation = savedState.getParcelable(KEY_LOCATION);
        }
        if (savedState.containsKey(KEY_LAST_UPDATED_TIME_STRING)) {
            lastUpdateTime = savedState.getString(KEY_LAST_UPDATED_TIME_STRING);
        }
        
        updateUI();
        
    }

}
</pre>
<p>ابتدا بررسی شده چنانچه savedState خالی نباشد (یعنی قبلا داده‌هایی در Bundle ذخیره شده باشد) سایر شروط را اجرا کن.<br />
قبلا در متد onSaveInstanceState داده‌ها را با سه کلید ذخیره کردیم. اینجا Bundle بررسی می‌شود و چنانچه کلیدها وجود داشتند؛ یعنی saveState.containsKey(KEY_NAME) خالی نبود، مقدار آن را در متغیر مربوطه قرار بده. لغت contains به معنی &#8220;شامل بودن&#8221; است.<br />
در قسمت قبل، داده‌ها ذخیره (Put) و در این قسمت، دریافت (Get) شده‌اند.<br />
در نهایت، بروزرسانی داده‌ها توسط متد updateUI انجام خواهد شد که در ادامه مبحث توضیح خواهم داد.</p>
<p class="pdfdl">جهت مطالعه ادامه آموزش، فایل PDF را دانلود نمائید</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<p class="copy">با توجه به اینکه آموزش‌های پایه با قیمت پایین در اختیار کاربر قرار گرفته و درآمد حاصل صرف تامین هزینه‌های وب سایت و تهیه آموزش‌های آتی می‌شود، به اشتراک گذاری این فایل با دیگران خلاف اخلاق است.</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه کامل این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۳۴<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : ۴۰ هزار تومان<br />
<span class="notice">توجه: صرفا در صورتی از درگاه پشتیبان استفاده کنید که قادر به پرداخت از طریق سبد دانلود نباشید.</span><br />
<a href="https://android-studio.ir/checkout/?edd_action=add_to_cart&#038;download_id=185463" class="button green edd-submit" rel="noopener noreferrer"><i class="titleicon fa fa-shopping-cart fa-lg"></i>افزودن به سبد دانلود</a> <a href="https://atisdesign.ir/android-files/" class="button red edd-submit" target="_blank" rel="noopener noreferrer">درگاه پشتیبان</a>
</div>
<p>نوشته <a href="https://android-studio.ir/android-location-using-googleapi/">مکان یابی در اندروید توسط GoogleApi</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/android-location-using-googleapi/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
			</item>
		<item>
		<title>نقشه سیدار مپ و API های آن</title>
		<link>https://android-studio.ir/cedarmaps-android-sdk-and-apis-tutorial/</link>
					<comments>https://android-studio.ir/cedarmaps-android-sdk-and-apis-tutorial/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Tue, 25 Feb 2020 13:17:56 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=172989</guid>

					<description><![CDATA[<p>در جلسه گذشته به نحوه نمایش نقشه Google Maps در اندروید پرداختیم. به دلیل محدودیت‌هایی که به واسطه پرداخت ارزی جهت استفاده از API های گوگل مپ برای توسعه دهندگان و برنامه نویسان اندرویدی داخل ایران وجود دارد تصمیم گرفتیم نحوه کار با یکی از سرویس دهنده‌های نقشه ایرانی را آموزش دهیم. در این جلسه [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/cedarmaps-android-sdk-and-apis-tutorial/">نقشه سیدار مپ و API های آن</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmaps_android_tutorial.png" alt="آموزش نقشه سیدار مپ (CedarMaps) در برنامه نویسی اندروید" /></p>
<p>در جلسه گذشته به نحوه <a href="http://android-studio.ir/add-google-map-to-android-app/" target="_blank" rel="noopener noreferrer">نمایش نقشه Google Maps در اندروید</a> پرداختیم. به دلیل محدودیت‌هایی که به واسطه پرداخت ارزی جهت استفاده از API های گوگل مپ برای توسعه دهندگان و برنامه نویسان اندرویدی داخل ایران وجود دارد تصمیم گرفتیم نحوه کار با یکی از سرویس دهنده‌های نقشه ایرانی را آموزش دهیم. در این جلسه و جلسات بعد به آموزش استفاده از نقشه سیدار مپ (CedarMaps) در اندروید می‌پردازیم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید:</b></p>
<ul>
<li>معرفی نقشه سیدار مپ و API های آن</li>
<li>نحوه اضافه کردن آنلاین و آفلاین کتابخانه SDK سیدار مپ در اندروید استودیو و رفع مشکل احتمالی</li>
<li>مجوز یا Permission های مورد نیاز برای استفاده از سیدار مپ و API ها</li>
<li>نحوه استفاده از ClientID و ClientSecret دریافتی از پشتیبانی سیدار مپ</li>
<li>استفاده از منوی BottomNavigationView برای نمایش فرگمنت‌ها</li>
<li>دریافت مجوز دسترسی به موقعیت مکانی توسط متد MapBox</li>
<li>پیاده سازی API مکان یابی و نمایش نقشه</li>
<li>پیاده سازی API نقطه یابی (Reverse Geocoding) و تبدیل نقطه به آدرس</li>
<li>پیاده سازی API جستجو بر اساس نام مکان (Forward Geocoding)</li>
<li>پیاده سازی API مسیریابی و تخمین مسافت (Direction)</li>
</ul>
</div>
<p class="copy">تصاویر نهایی پروژه:</p>
<div class="col-lg-12 demoes">
<div class="col-lg-3 col-md-6 col-sm-12">
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmap_location_demo.png" alt="نمایش نقشه سیدار مپ در برنامه نویسی اندروید و یافتن موقعیت مکانی کاربر روی نقشه" />
</div>
<div class="col-lg-3 col-md-6 col-sm-12">
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmap_reverse_geocode_demo.jpg" alt="تبدیل نقطه جغرافیایی به آدرس در نقشه سیدار مپ" />
</div>
<div class="col-lg-3 col-md-6 col-sm-12">
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmap_forward_geocode_demo.jpg" alt="جستجوی نقاط جغرافیایی بر اساس نام اماکن مانند شهر، منطقه، نام خیابان و... در CedarMaps" />
</div>
<div class="col-lg-3 col-md-6 col-sm-12">
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmap_direction_demo.jpg" alt="مسیریابی و تخمین مسافت در نقشه سیدار مپ در برنامه نویسی اندروید" />
</div>
</div>
<p>&nbsp;</p>
<p class="copy">این جلسه در قالب PDF و در ۶۹ صفحه تهیه شده که در ادامه چند صفحه‌ ابتدایی را مشاهده می‌کنید:</p>
<p></p>
<h2>چرا سیدار مپ؟</h2>
<p>اگر آموزش جلسه قبل را مطالعه کرده باشید گفتیم که مدتیست گوگل سیاست خود در قبال ارائه API های مرتبط با Google Map را تغییر داده و برای استفاده از پلن رایگان و محدود هم نیاز به احراز هویت از طریق Billing دارد که تهیه آن برای اکثر برنامه نویسان و توسعه دهندگان داخل ایران ممکن نیست. ضمن اینکه هر لحظه ممکن است تحریم‌های جدیدی روی این سرویس‌ها اعمال شده و به عنوان مثال حتی امکان استفاده رایگان از SDK نقشه هم وجود نداشته باشد. بنابراین انتخاب منطقی این است که از سایر گزینه‌های روی میز استفاده کنیم!<br />
سرویس‌های داخلی متعددی در حال حاضر بر سر ارائه API نقشه به رقابت با یکدیگر پرداخته‌اند که می‌توان به <a href="https://www.cedarmaps.com/" target="_blank" rel="noopener noreferrer">سیدار مپ</a>، <a href="https://neshan.org/" target="_blank" rel="noopener noreferrer">نشان</a> و <a href="https://map.ir/" target="_blank" rel="noopener noreferrer">مپ</a> اشاره کرد. هرکدام از این سرویس‌ها SDK مربوط به اندروید را در اختیار توسعه دهندگان قرار داده‌اند که با اضافه کردن آن به پروژه می‌توان از API ها و قابلیت‌های مختلف از جمله نمایش موقعیت مکانی روی نقشه، مکان یابی، مسیریابی و تبدیل نقطه جغرافیایی به آدرس استفاده کرد.<br />
هرکدام از این سرویس دهنده‌ها مزایا و معایبی داشته و علاوه بر آن در نحوه تعرفه گذاری نیز تفاوت‌هایی دارند. </p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmaps_plans_prices.png" alt="تعرفه‌های نقشه سیدار مپ" /></p>
<p>تصویر فوق مربوط به تعرفه‌های سیدار مپ در زمان تهیه این آموزش است. پلن رایگان آن شامل ۲۰۰۰ API Call در روز است. یعنی تا ۲۰۰۰ درخواست از سمت کاربران برنامه ما بطور رایگان پاسخ داده می‌شود. این درخواست‌ها شامل نمایش موقعیت مکانی کاربر، تبدیل موقعیت جغرافیایی به آدرس (شامل استان، شهر، منطقه، خیابان، کوچه و&#8230;)، مسیریابی و تخمین مسافت خواهد بود که توسط کاربران برنامه ارسال می‌شود.<br />
همچنین در پلن رایگان نمایش و لود نقشه تا سقف ۱۰۰۰۰ مرتبه در روز امکان پذیر است. بنابراین واضح است که صرف نمایش نقشه روی برنامه، یک API Call محسوب نمی‌شود و فقط مصرف پهنای باند را در پی خواهد داشت که در این پلن تا ۱۰۰ گیگابایت در ماه تعیین شده.<br />
با توجه به مشخصات پلن رایگان CedarMap می‌توان گفت برای اکثر اپلیکیشن‌های با مخاطب کم، همین پلن کفایت کرده و حداقل برای ابتدای کار نیازی به ارتقاء به پلن‌های بالاتر و پرداخت هزینه نیست. برای استفاده از پلن رایگان باید درخواست خود را در قسمت مربوطه داخل سایت سیدار مپ ارسال کنید. معمولا در کمتر از ۲۴ ساعت اطلاعات مورد نیاز برای استفاده از نقشه به ایمیل شما ارسال خواهد شد. این اطلاعات شامل یک Client ID و Client Secret است که باید در پروژه تعریف شود.<br />
اگر صفحه نخست وب سایت سیدار مپ را بررسی کرده باشید عمده تمرکز آن روی مقایسه نقشه گوگل و سیدار مپ است. از جمله خوانایی و زیبایی، سرعت، جزئیات نقشه، دقت بیشتر در مسیریابی و&#8230; که در تمام موارد برتری سیدار مپ نتیجه گرفته شده. البته که بنده تمام این برتری‌ها را تایید نمی‌کنم و در برخی موارد اغراق شده. حتی در تست‌ها مشخص شد در برخی موارد از جمله تبدیل موقعیت جغرافیایی به آدرس (بخصوص در مناطقی غیر از کلان شهرها) سیدار مپ از دقت کمتری برخوردار بوده و هنوز تا رسیدن به حد مطلوب جای کار دارد.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmaps_readability.jpg" alt="خوانایی نقشه سیدار مپ" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmaps_map_speed_load.jpg" alt="سرعت بالای لود نقشه CedarMaps" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmaps_details_on_map.jpg" alt="جزئیات نقشه سیدار مپ" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmaps_reverse_geocoding.jpg" alt="API تبدیل نقطه به آدرس نقشه سیدار مپ" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedramaps_forward_geocoding.jpg" alt="API جستجو در معابر سیدار مپ " /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmaps_direction.jpg" alt="API مسیریابی و تخمین مسافت سیدار مپ" /></p>
<p>با اینحال از نظر بنده و در مقایسه‌ای که بین چند سرویس دهنده داخلی انجام دادم در نهایت این سرویس را انتخاب کردم. </p>
<h2>پیاده سازی نقشه سیدار مپ در اپلیکیشن اندرویدی</h2>
<p>در صفحه <a href="http://api.cedarmaps.com/docs/" target="_blank" rel="noopener noreferrer">مستندات نقشه سیدار مپ</a> اطلاعات مربوط به نحوه پیاده سازی نقشه و API های آن در اندروید، iOS و وب در دسترس توسعه دهندگان قرار گرفته است:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/cedarmaps_android_api_document.png" alt="مستندات API های نقشه سیدار مپ" /></p>
<p>با کلیک روی گزینه Android به صفحه SDK سیدار مپ برای پلتفرم اندروید در گیت هاب منتقل می‌شوم:</p>
<p style="text-align: left; direction: ltr;">
<a href="https://github.com/cedarstudios/cedarmaps-android-sdk" target="_blank" rel="noopener noreferrer">https://github.com/cedarstudios/cedarmaps-android-sdk</a>
</p>
<p>در این صفحه توضیحاتی در خصوص نحوه اضافه کردن کتابخانه SDK سیدار مپ به پروژه اندرویدی در <a href="http://android-studio.ir/">اندروید استودیو</a> توضیحاتی ارائه شده. همچنین به این نکته اشاره شده که SDK سیدار مپ بر پایه SDK <a href="https://www.mapbox.com/" target="_blank" rel="noopener noreferrer">مپ باکس (MapBox)</a> ساخته شده و امکاناتی به آن اضافه شده است. MapBox خود یک سرویس دهنده نقشه جایگزین گوگل مپ بوده که البته برخلاف سیدار مپ ایرانی نیست.<br />
بیشتر از این درگیر مباحث تئوری نشده و در ادامه آموزش به نحوه کار با نقشه سیدار مپ در اندروید استودیو می‌پردازم.</p>
<h2>ساخت پروژه اندرویدی سیدار مپ</h2>
<p>در این پروژه قصد داریم در قالب چند فرگمنت، امکانات و API های مختلف نقشه سیدار مپ را آموزش دهیم. طبق <a href="http://android-studio.ir/create-android-project-and-its-structure/" target="_blank" rel="noopener noreferrer">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه با نام CedarMap با یک Empty Activity و زبان Java ایجاد می‌کنم.<br />
ابتدا مطابق توضیحات موجود در صفحه گیت هاب سیدار مپ اندروید، هردو فایل build.gradle سطح project و app را ویرایش می‌کنم. در قدم نخست و در build.gradle سطح پروژه، آدرس مخزن سیدار مپ را در بلاک repositories زیرمجموعه بلاک allprojects اضافه می‌کنم:</p>
<p><span class="filename">build.gradle (Project)</span></p>
<pre class="brush: java; title: ; notranslate">
buildscript {
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
        
    }
}

allprojects {
    repositories {
        jcenter()
        google()

        maven {
            url &quot;https://repo.cedarmaps.com/android/&quot;
        }
        
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
</pre>
<p>در قدم بعد فایل build.gradle سطح app را ویرایش می‌کنم. برای جلوگیری از بروز خطای مربوط به نسخه جاوا، مطابق توضیحات موجود در صفحه گیت هاب سیدار مپ یک بلاک با نام compileOptions به بلاک android باید اضافه شود:</p>
<p><span class="filename">build.gradle (app)</span></p>
<pre class="brush: java; title: ; notranslate">
android {
    compileSdkVersion 29
    buildToolsVersion &quot;29.0.3&quot;
    defaultConfig {
        applicationId &quot;ir.android_studio.cedarmap&quot;
        minSdkVersion 19
        targetSdkVersion 29
        versionCode 1
        versionName &quot;1.0&quot;
        testInstrumentationRunner &quot;androidx.test.runner.AndroidJUnitRunner&quot;
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
</pre>
<p>در ادامه کتابخانه‌های مورد نیاز برای نقشه را در بلاک dependencies اضافه و پروژه را سینک می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
dependencies {
    implementation fileTree(dir: 'libs', include: &#x5B;'*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.cedarmaps:CedarMapsSDK:4.2.1'
    implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v7:0.6.0'
    implementation 'com.google.android.material:material:1.2.0-alpha04'
    implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
}
</pre>
<p>کتابخانه اصلی و ضروری سیدار مپ مورد اول یعنی CedarMapsSDK است که مربوط به SDK نقشه می‌باشد. ۳ مورد دیگر به ترتیب برای استفاده از سایر امکانات SDK مپ باکس، اضافه کردن دکمه FAB و Bottom Navigation و ترسیم تصاویر وکتوری تعریف شده‌اند.</p>
<div class="alert alert-warning">
<span class="notice">تذکر:</span> به دلیل اینکه SDK سیدار مپ از سرور اختصاصی سیدار به نشانی https://repo.cedarmaps.com/android دریافت می‌شود ممکن است در دریافت کتابخانه و سینک شدن پروژه با مشکل مواجه شوید. ابتدا مطمئن شوید ابزار تغییر IP شما از نوعی است که محدود به دامنه‌های خاص نبوده و همه url ها را می‌تواند از درگاه خود عبور دهد. در غیر اینصورت امکان اتصال به سرور سیدار میسر نخواهد بود. سرویس‌های تغییر آی‌پی مانند &#8220;FOD&#8221; و &#8220;شکن&#8221; روی دامنه‌های محدودی تنظیم شده و در این مورد قابل استفاده نیستند. برای انتخاب ابزار مناسب، صفحه <a href="http://android-studio.ir/how-to-bypass-software-sanctions/" target="_blank" rel="noopener noreferrer">نحوه فعال کردن پروکسی در اندروید استودیو</a> را مطالعه نمائید.<br />
البته ممکن است با رعایت نکات فوق باز هم موفق به سینک کردن پروژه نشوید. مانند آنچه برای من اتفاق افتاد! مکاتبات زیادی هم با بخش پشتیبانی سیدار مپ داشتم اما ظاهرا نه سرور ایرادی دارد نه اندروید استودیو من. بنابراین تنها پیشنهادی که ارائه شد اضافه کردن آفلاین کتابخانه CedarMapsSDK بود که لینک دانلود آن را در ایمیل ارسال کردند.<br />
نحوه افزودن آفلاین کتابخانه‌ها در اندروید استودیو را قبلا در مطلب <a href="http://android-studio.ir/activity/" target="_blank" rel="noopener noreferrer">اکتیویتی‌ها در اندروید</a> توضیح داده‌ام. ابتدا فایل .jar یا .aar کتابخانه در پوشه app>libs قرار می‌گیرد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/add_offline_CedarMaps_SDK_to_android_studio.png" alt="اضافه کردن کتابخانه SDK نقشه سیدار مپ به صورت آفلاین" /></p>
<p>SDK سیدار مپ از سه کتابخانه دیگر استفاده می‌کند. بنابراین لازم است در حالت نصب آفلاین کتابخانه سیدار مپ، این ۳ کتابخانه را جداگانه تعریف کنیم که به ترتیب mapbox-android-sdk، okhttp و gson هستند. در نهایت لیست کتابخانه‌ها به اینصورت است:</p>
<pre class="brush: java; title: ; notranslate">
dependencies {
    implementation fileTree(dir: 'libs', include: &#x5B;'*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation files('libs/CedarMapsSDK-4.2.1.aar')
    api 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.4.0'
    implementation 'com.squareup.okhttp3:okhttp:3.12.6'
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v7:0.6.0'
    implementation 'com.google.android.material:material:1.2.0-alpha04'
    implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
}
</pre>
<p>حالا پروژه را سینک می‌کنم. در حالت آفلاین از ابزارهای تغییر آی‌پی مانند FOD هم می‌توان استفاده کرد زیرا دیگر نیازی به اتصال به سرور سیدار مپ نداریم.<br />
در صورتی که نسخه جدیدتری از CedarMapsSDK منتشر شده بود برای دریافت فایل نسخه جدید به پشتیبانی سیدار مپ ایمیل زده و یا ورژن را در لینک زیر جایگزین کنید:</p>
<p style="text-align: left; direction: ltr;">
https://repo.cedarmaps.com/android/com/cedarmaps/CedarMapsSDK/4.2.1/CedarMapsSDK-4.2.1.aar
</p>
<p>حالا پروژه را سینک می‌کنم.
</p></div>
<p>در مرحله بعد باید دسترسی‌های مورد نیاز نقشه سیدار مپ را در مانیفست پروژه تعریف کنیم که در مطلب پیاده سازی Runtime Permission در اندروید به طور مفصل با پرمیشن‌ها آشنا شدیم. </p>
<p><span class="filename">AndroidManifest.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.cedarmap&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;
    &lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot;/&gt;
    
    &lt;application
        android:allowBackup=&quot;true&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/AppTheme&quot;&gt;
        &lt;activity android:name=&quot;.MainActivity&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;

&lt;/manifest&gt;
</pre>
<p>دو مجوز دسترسی به اینترنت و موقعیت مکانی در مانیفست تعریف شد. در صورتی که بخواهیم مانند جلسه قبل صرفا یک موقعیت جغرافیایی از پیش تعیین شده را روی نقشه نشان دهیم مجوز دسترسی به اینترنت برای دریافت تایل‌های نقشه و نمایش آنها کفایت می‌کند. اما ما به دریافت موقعیت فعلی کاربر هم نیاز داریم.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> اگر هنگام لود نقشه‌های آنلاین دقت کرده باشید نقشه به صورت قسمت‌های مربعی که کنار یکدیگر قرار می‌گیرند بارگزاری می‌شود که به هرکدام از این قسمت‌ها یک تایل (Tile به معنی کاشی) گفته می‌شود.
</div>
<p>در توضیحات صفحه گیت هاب قید شده که ClientID و ClientSecret بهتر است در یک subclass از کلاس Application پروژه تعریف شود. بنابراین یک کلاس با نام دلخواه CedarID به پروژه اضافه کرده و آنرا از کلاس Application ارث بری می‌کنم. سپس متد <span dir="ltr">onCreate()</span> را به کلاس اضافه و کد مربوطه را درون متد قرار می‌دهم:</p>
<p><span class="filename">CedarID.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.cedarmap;

import android.app.Application;

import com.cedarstudios.cedarmapssdk.CedarMaps;
import com.cedarstudios.cedarmapssdk.model.MapID;

public class CedarID extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        CedarMaps.getInstance()
                .setClientID(&quot;YOUR_CLIENT_ID&quot;)
                .setClientSecret(&quot;YOUR_CLIENT_SECRET&quot;)
                .setContext(this)
                .setMapID(MapID.MIX);
    }
    
}
</pre>
<p>کافیست در کد فوق، دو کد دریافتی از سیدار مپ را جایگزین کنید. توجه داشته باشید subclass کلاس Application باید درون مانیفست تعریف شود. برای اینکار کافیست ویژگی name به تگ application مانیفست اضافه شود که مقدار آن، نام کلاس است:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;application
    android:name=&quot;.CedarID&quot;
    android:allowBackup=&quot;true&quot;
    android:icon=&quot;@mipmap/ic_launcher&quot;
    android:label=&quot;@string/app_name&quot;
    android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
    android:supportsRtl=&quot;true&quot;
    android:theme=&quot;@style/AppTheme&quot;&gt;
    &lt;activity android:name=&quot;.MainActivity&quot;&gt;
        &lt;intent-filter&gt;
            &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

            &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
        &lt;/intent-filter&gt;
    &lt;/activity&gt;
&lt;/application&gt;
</pre>
<p>در layout اکتیویتی یک FrameLayout برای نمایش <a href="http://android-studio.ir/android-fragments/" target="_blank" rel="noopener noreferrer">فرگمنت‌</a>ها و یک <a href="http://android-studio.ir/android-bottom-navigation/" target="_blank" rel="noopener noreferrer">BottomNavigationView‌</a> برای نمایش منوی پایین صفحه تعریف می‌کنم:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    android:orientation=&quot;vertical&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;FrameLayout
        android:id=&quot;@+id/content&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;0dp&quot;
        android:layout_weight=&quot;1&quot;&gt;

    &lt;/FrameLayout&gt;

    &lt;com.google.android.material.bottomnavigation.BottomNavigationView
        android:id=&quot;@+id/navigationView&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_gravity=&quot;bottom&quot;
        android:background=&quot;?android:attr/windowBackground&quot;
        app:labelVisibilityMode=&quot;labeled&quot;
        app:menu=&quot;@menu/navigation&quot; /&gt;

&lt;/LinearLayout&gt;
</pre>
<p>منوی BottomNavigation شامل ۴ آیتم است که با نام navigation.xml و در قالب یک menu در پروژه تعریف شده:</p>
<p><span class="filename">navigation.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;menu xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;

    &lt;item
        android:id=&quot;@+id/navigation_map&quot;
        android:icon=&quot;@drawable/ic_map_marker&quot;
        android:title=&quot;نقشه&quot; /&gt;

    &lt;item
        android:id=&quot;@+id/navigation_reverse&quot;
        android:icon=&quot;@drawable/ic_reverse_geocode&quot;
        android:title=&quot;نقطه یابی&quot; /&gt;

    &lt;item
        android:id=&quot;@+id/navigation_forward&quot;
        android:icon=&quot;@drawable/ic_forward_geocode&quot;
        android:title=&quot;جستجو&quot; /&gt;

    &lt;item
        android:id=&quot;@+id/navigation_direction&quot;
        android:icon=&quot;@drawable/ic_direction&quot;
        android:title=&quot;مسیریابی&quot; /&gt;

&lt;/menu&gt;
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> ساختار نهایی پروژه در صفحه ۶۰ نمایش داده شده است.
</div>
<p>حالا در اکتیویتی کدهای لازم را اضافه می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.cedarmap;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import com.google.android.material.bottomnavigation.BottomNavigationView;


public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
</pre>
<p>ابتدا کلاس اکتیویتی را implement می‌کنم به BottomNavigationView.OnNavigationItemSelectedListener. سپس با قرار دادن نشانگر موس روی بدنه کلاس و زدن alt + enter متد onNavigationItemSelected را به کلاس اضافه می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/cedarmaps/add_onNavigationItemSelected_method_to_activity.png" alt="اضافه کردن متد onNavigationItemSelected برای مدیریت API های نقشه سیدار مپ" /></p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.cedarmap;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.MenuItem;

import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.mapbox.mapboxsdk.geometry.LatLng;


public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {

    public static final LatLng VANAK_SQUARE = new LatLng(35.7572, 51.4099);
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        BottomNavigationView navigation = findViewById(R.id.navigationView);
        navigation.setOnNavigationItemSelectedListener(MainActivity.this);
        navigation.setSelectedItemId(R.id.navigation_map);

    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        return false;
    }
}
</pre>
<p>یک LatLng با نام VANAK_SQUARE تعریف شده که مختصات میدان ونک تهران در آن ذخیره شده. از این نقطه برای نمایش محل پیش فرض نقشه استفاده خواهیم کرد.<br />
درون متد <span dir="ltr">onCreate()</span> اکتیویتی ویجت BottomNavigation را تعریف کرده و همچنین مشخص کردم آیتم با شناسه navigation_map به صورت پیش فرض و هنگام اجرای اپلیکیشن انتخاب شده باشد که مربوط به فرگمنت نمایش نقشه و موقعیت مکانی فرد خواهد بود.<br />
متد onNavigationItemSelected را به صورت زیر تکمیل می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    Fragment fragment = null;
    switch (item.getItemId()) {
        case R.id.navigation_map:
            setTitle(&quot;نقشه&quot;);
            fragment = new MapFragment();
            break;
        case R.id.navigation_reverse:
            setTitle(&quot;نقطه یابی&quot;);
            fragment = new ReverseGeocodeFragment();
            break;
        case R.id.navigation_forward:
            setTitle(&quot;&quot;);
            fragment = new ForwardGeocodeFragment();
            break;
        case R.id.navigation_direction:
            setTitle(&quot;مسیریابی&quot;);
            fragment = new DirectionFragment();
            break;
    }

    if (fragment != null) {
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        if (getSupportFragmentManager().getFragments().isEmpty()) {
            transaction.add(R.id.content, fragment, String.format(Locale.US, &quot;item: %d&quot;, item.getItemId())).commit();
        } else {
            transaction.replace(R.id.content, fragment, String.format(Locale.US, &quot;item: %d&quot;, item.getItemId())).commit();
            invalidateOptionsMenu();
        }
        return true;
    }

    return false;
}

با لمس هریک از آیتم‌های منو، فرگمنت مربوطه اجرا خواهد شد.
دسترسی به موقعیت جغرافیایی کاربر جزء مجوزهای خطرناک (Dangerous Permissions) بحساب می‌آید بنابراین طبق مبحث Runtime Permission باید برای اندروید ۶ و به بالا هنگام اجرای برنامه مجوز مربوطه را از کاربر دریافت کنیم. برای اینکار متد onRequestPermissionsResult را در کلاس اکتیویتی Override می‌کنم:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {
    
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
</pre>
<p>متد را به صورت زیر تکمیل می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {

    List allFragments = getSupportFragmentManager().getFragments();
    if (allFragments.isEmpty()) {
        return;
    }

    Fragment currentFragment = (Fragment) allFragments.get(allFragments.size() - 1);
    if (currentFragment instanceof PermissionsListener) {
        currentFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
        return;
    }

    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
</pre>
<p>ابتدا بررسی می‌شود آیا فرگمنتی وجود دارد یا نه. در صورتی که جواب منفی بود (یعنی allFragments برابر با isEmpty باشد) هیچ کاری انجام نشده و return می‌شود. در ادامه شرطی را تعریف می‌کنم تا صرفا هنگامی مجوز دسترسی به موقعیت مکانی از کاربر دریافت شود که به آن نیاز داشته باشیم. به عنوان مثال در API جستجو بر اساس نام مکان و یا مسیریابی، نیازی به موقعیت کاربر نیست و چنانچه شخص صرفا از این قابلیت استفاده کند منطقی نیست دسترسی موقعیت را درخواست کنیم.<br />
ابتدا فرگمنت در حال اجرا در currentFragment قرار می‌گیرد. سپس بررسی می‌کنیم اگر از فرگمنت موجود در currentFragment درخواست مجوز دسترسی ارسال شده، پیغام مربوطه را به کاربر نمایش بده تا دسترسی را تایید یا لغو کند. در اینجا خبری از کدهای طولانی Runtime Permission پیش فرض اندروید نیست و مدیریت اینکار توسط اینترفیس PermissionsListener انجام می‌شود که مربوط به MapBox است. در واقع MapBox توسعه دهنده را از نوشتن کدهای اضافی بی نیاز کرده. البته استفاده از این قابلیت مپ باکس الزامی نبوده و می‌توان به صورت عادی و طبق آنچه در مبحث مربوط به Runtime Permission تمرین کرده بودیم مجوز دسترسی به موقعیت جغرافیایی را هنگام اجرای برنامه یا هنگام اجرای فرگمنت مدنظر از کاربر دریافت کنیم.</p>
<pre class="brush: java; title: ; notranslate">
if (currentFragment instanceof PermissionsListener)
</pre>
<p>در خط فوق بررسی می‌شود آیا فرگمنت ذخیره شده در متغیر currentFragment از جنس PermissionsListener هست یا نه. به عبارت دیگر بررسی می‌شود آیا در فرگمنت فعلی مجوزی نیاز هست که مربوط به MapBox باشد یا خیر. به طور خلاصه دستور instanceof در جاوا وظیفه برگرداندن نتیجه مقایسه جنس دو آیتم را بر عهده دارد.<br />
کد نهایی اکتیویتی:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.cedarmap;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;

import android.os.Bundle;
import android.view.MenuItem;

import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.mapbox.android.core.permissions.PermissionsListener;
import com.mapbox.mapboxsdk.geometry.LatLng;

import java.util.List;
import java.util.Locale;


public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {

    public static final LatLng VANAK_SQUARE = new LatLng(35.7572, 51.4099);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        BottomNavigationView navigation = findViewById(R.id.navigationView);
        navigation.setOnNavigationItemSelectedListener(MainActivity.this);
        navigation.setSelectedItemId(R.id.navigation_map);

    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        Fragment fragment = null;
        switch (item.getItemId()) {
            case R.id.navigation_map:
                setTitle(&quot;نقشه&quot;);
                fragment = new MapFragment();
                break;
            case R.id.navigation_reverse:
                setTitle(&quot;نقطه یابی&quot;);
                fragment = new ReverseGeocodeFragment();
                break;
            case R.id.navigation_forward:
                setTitle(&quot;&quot;);
                fragment = new ForwardGeocodeFragment();
                break;
            case R.id.navigation_direction:
                setTitle(&quot;مسیریابی&quot;);
                fragment = new DirectionFragment();
                break;
        }

        if (fragment != null) {
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            if (getSupportFragmentManager().getFragments().isEmpty()) {
                transaction.add(R.id.content, fragment, String.format(Locale.US, &quot;item: %d&quot;, item.getItemId())).commit();
            } else {
                transaction.replace(R.id.content, fragment, String.format(Locale.US, &quot;item: %d&quot;, item.getItemId())).commit();
                invalidateOptionsMenu();
            }
            return true;
        }

        return false;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {

        List allFragments = getSupportFragmentManager().getFragments();
        if (allFragments.isEmpty()) {
            return;
        }

        Fragment currentFragment = (Fragment) allFragments.get(allFragments.size() - 1);
        if (currentFragment instanceof PermissionsListener) {
            currentFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
            return;
        }

        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

}
</pre>
<p class="pdfdl">جهت مطالعه ادامه آموزش، فایل PDF را دانلود نمائید</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<p class="copy">با توجه به اینکه آموزش‌های پایه با قیمت پایین در اختیار کاربر قرار گرفته و درآمد حاصل صرف تامین هزینه‌های وب سایت و تهیه آموزش‌های آتی می‌شود، به اشتراک گذاری این فایل با دیگران خلاف اخلاق است.</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه کامل این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۶۹<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۳ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : ۳۶ هزار تومان<br />
<span class="notice">توجه: صرفا در صورتی از درگاه پشتیبان استفاده کنید که قادر به پرداخت از طریق سبد دانلود نباشید.</span><br />
<a href="https://android-studio.ir/checkout/?edd_action=add_to_cart&#038;download_id=173026" class="button green edd-submit" rel="noopener noreferrer"><i class="titleicon fa fa-shopping-cart fa-lg"></i>افزودن به سبد دانلود</a> <a href="https://atisdesign.ir/android-files/" class="button red edd-submit" target="_blank" rel="noopener noreferrer">درگاه پشتیبان</a>
</div>
<p>نوشته <a href="https://android-studio.ir/cedarmaps-android-sdk-and-apis-tutorial/">نقشه سیدار مپ و API های آن</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/cedarmaps-android-sdk-and-apis-tutorial/feed/</wfw:commentRss>
			<slash:comments>49</slash:comments>
		
		
			</item>
		<item>
		<title>نمایش نقشه Google Map در اندروید</title>
		<link>https://android-studio.ir/add-google-map-to-android-app/</link>
					<comments>https://android-studio.ir/add-google-map-to-android-app/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Mon, 13 Jan 2020 16:36:21 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=170358</guid>

					<description><![CDATA[<p>امروزه نقشه‌ها کاربرد گسترده‌ای در اپلیکیشن‌ها و وب سایت‌ها داشته و کاربر به وسیله آن می‌تواند اطلاعات موردنیاز خود را دریافت کرده و یا درخواستی را ارسال کند. در فصل دوازدهم قصد دارم کار با نقشه‌ها را آموزش دهم. در این جلسه از سری مباحث آموزش برنامه نویسی اندروید، نحوه پیاده سازی نقشه Google Map [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/add-google-map-to-android-app/">نمایش نقشه Google Map در اندروید</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/android_google_map_api_tutorial.png" alt="آموزش استفاده از نقشه Google Map در برنامه نویسی اندروید" /><br />
امروزه نقشه‌ها کاربرد گسترده‌ای در اپلیکیشن‌ها و وب سایت‌ها داشته و کاربر به وسیله آن می‌تواند اطلاعات موردنیاز خود را دریافت کرده و یا درخواستی را ارسال کند. در فصل دوازدهم قصد دارم کار با نقشه‌ها را آموزش دهم. در این جلسه از سری مباحث <a href="http://android-studio.ir/" target="_blank" rel="noopener noreferrer">آموزش برنامه نویسی اندروید</a>، نحوه پیاده سازی نقشه Google Map در اندروید را بررسی می‌کنیم.</p>
<h2>امکانات نقشه Google Map اندروید</h2>
<p>به نام خدا. نقشه گوگل مپ امکانات و قابلیت‌های متعددی را در اختیار توسعه دهندگان و برنامه نویسان اندرویدی و سایر پلتفرم‌ها قرار داده است. از جمله این قابلیت‌ها می‌توان به نمایش نقشه جهان، نمایش موقعیت مکانی شخص، جستجو بر اساس نام اماکن و شهرها، مسیریابی، نمایش اطلاعات اماکن، نمایش نقشه ماهواره‌ای و&#8230; اشاره کرد.<br />
متاسفانه مدتیست گوگل سیاست خود در نحوه ارائه API نقشه را تغییر داده و مانند قبل امکان استفاده رایگان از این سرویس وجود ندارد. البته گوگل یک پلن رایگان و به صورت محدود ارائه داده که برای هریک از قابلیت‌های نقشه، تعداد درخواست معینی را در یک ماه می‌توان ارسال کرد. با اینحال فعالسازی این پلن رایگان هم نیاز به ارائه مشخصات کارت بانکی یا به اصطلاح Billing دارد که برای برنامه نویسان داخل ایران خبر خوبی نیست!</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/google_map_pricing_plans.png" alt="قیمت‌های استفاده از API سرویس نقشه Google Map در اندروید" /></p>
<p>البته صرفا نمایش نقشه برای سیستم عامل‌های موبایل بدون ثبت اطلاعات بانکی هم امکان پذیر است. بنابراین در صورتی که به سایر امکانات نقشه مانند جستجو و مسیریابی نیازی نداشته و هدف صرفا نمایش یک موقعیت مکانی روی نقشه باشد، گوگل مپ گزینه مناسبی است.<br />
در این جلسه ابتدا در Console گوگل یک API برای نمایش نقشه فعال کرده سپس با استفاده از کلید API، نقشه Google Map در اندروید را پیاده سازی می‌کنیم.</p>
<h2>ساخت API Key در Google Console</h2>
<p>برای استفاده از API نقشه گوگل ابتدا با یک اکانت گوگل وارد لینک زیر می‌شویم:</p>
<p style="text-align: left; direction: ltr;">
<a href="https://console.developers.google.com" target="_blank" rel="noopener noreferrer">https://console.developers.google.com</a>
</p>
<p>خدمات گوگل بر روی IP ایران محدود بوده و لازم است آی‌پی خود را تغییر دهیم. توصیه می‌کنم مطلب <a href="http://android-studio.ir/how-to-bypass-software-sanctions/" target="_blank" rel="noopener noreferrer">دور زدن تحریم‌های نرم افزاری</a> را مطالعه کنید.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/google_console.png" alt="کنسول Developer گوگل" /></p>
<p>روی Select a project و سپس NEW PROJECT کلیک می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/create_new_project_in_google_console.png" alt="ساخت پروژه جدید در گوگل کنسول" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/create_new_project_in_google_console_2.png" alt="ساخت پروژه جدید در گوگل کنسول" /></p>
<p>در اینجا یک نام برای پروژه تعریف می‌کنم. با کلیک روی دکمه CREATE پروژه ساخته می‌شود. سپس در قسمت Credentials روی دکمه Create credentials کلیک کرده و گزینه API key را انتخاب می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/create_api_key_in_google_console.png" alt="ساخت API Key در کنسول گوگل" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/create_api_key_in_google_console_2.png" alt="ساخت API Key در کنسول گوگل" /></p>
<p>بلافاصله یک کلید ساخته می‌شود. این کلید را می‌توان محدود به یک محیط خاص (اپلیکیشن، وب و&#8230;) کرد. یعنی با محدود کردن این کلید به اپلیکیشن، در وب سایت قابل استفاده نخواهد بود و بلعکس. این گزینه برای من اهمیتی ندارد بنابراین با RESTRICT KEY کاری ندارم.<br />
حالا در قسمت Dashboard پروژه روی دکمه ENABLE APIS AND SERVICES کلیک کرده و در مرحله بعد گزینه Maps SDK for Android را انتخاب و فعال می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/enable_google_map_api_in_google_console.png" alt="فعال کردن Google Map API در کنسول گوگل" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/enable_maps_sdk_for_android.png" alt="فعال کردن SDK نقشه گوگل برای اندروید" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/enable_maps_sdk_for_android_2.png" alt="فعال کردن SDK نقشه گوگل برای اندروید" /></p>
<p>فعلا با کنسول کاری نداریم. </p>
<h2>ساخت پروژه نمایش نقشه Google Map در اپلیکیشن اندروید</h2>
<p>در این پروژه قصد دارم یک موقعیت مکانی را به وسیله یک Marker نمایش دهم. طبق <a href="http://android-studio.ir/create-android-project-and-its-structure/" target="_blank" rel="noopener noreferrer">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه با نام Google Map می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم. البته بجای Empty Activity از یک Google Maps Activity می‌توان استفاده کرد که کار را ساده تر می‌کند. با اینحال می‌خواهم جزئیات را بررسی کنم و بهتر است که یک اکتیویتی معمولی را تکمیل کنیم.<br />
برای استفاده از API نقشه گوگل مپ در اندروید نیاز به ابزار Google Play Services در SDK داریم. بنابراین قبل از هر چیز ابتدا SDK Manager را بررسی کنید و در صورت عدم نصب این ابزار، نصب را انجام دهید:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/install_google_play_services_in_sdk_manager.png" alt="نصب Google Play Services در SDK اندروید استودیو" /></p>
<p>سپس باید کتابخانه play-services-maps که زیرمجموعه کتابخانه play-services هست را به پروژه اضافه کنیم. </p>
<p>نکته: استفاده از خود کتابخانه play-services به این دلیل که شامل چندین کتابخانه فرعی است توصیه نمی‌شود و صرفا کتابخانه‌های مورد نیاز پروژه را باید تعریف کرد. به این ترتیب از افزایش حجم برنامه جلوگیری می‌شود. </p>
<p>در مسیر File > Project Structure طبق تصویر زیر کتابخانه play-services-maps را در مخزن آنلاین کتابخانه‌های اندروید جستجو کرده و آخرین نسخه موجود را به پروژه اضافه می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/add_play-services-maps_library_to_android_project.png" alt="اضافه کردن کتابخانه play-services-maps به پروژه اندرویدی در اندروید استودیو" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/add_play-services-maps_library_to_android_project_2.png" alt="اضافه کردن کتابخانه play-services-maps به پروژه اندرویدی در اندروید استودیو" /></p>
<p>کتابخانه به بلاک dependencies فایل build.gradle اضافه شد:</p>
<pre class="brush: xml; title: ; notranslate">
dependencies {
    implementation fileTree(dir: 'libs', include: &#x5B;'*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.gms:play-services-maps:17.0.0'
}
</pre>
<p>نقشه به صورت آنلاین دریافت می‌شود بنابراین باید مجوز دسترسی به اینترنت را در مانیفست پروژه تعریف کنم:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&gt;
</pre>
<p>همچنین یک تگ meta-data لازم است در تگ application مانیفست تعریف شود که شامل دو ویژگی name و value است:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;meta-data
    android:name=&quot;com.google.android.geo.API_KEY&quot;
    android:value=&quot;YOUR_API_KEY&quot; /&gt;
</pre>
<p>برای name مقدار com.google.android.geo.API_KEY و برای value کلیدی که در کنسول گوگل ساخته بودیم را قرار می‌دهیم. با اضافه شدن این تگ در مانفیست، گوگل بررسی می‌کند کلید تعریف شده قبلا در کنسول ایجاد شده باشد در غیر اینصورت نقشه لود نخواهد شد.</p>
<p>مانیفست به اینصورت تکمیل شد:</p>
<p><span class="filename">AndroidManifest.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.googlemap&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&gt;

    &lt;application
        android:allowBackup=&quot;true&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/AppTheme&quot;&gt;

        &lt;meta-data
            android:name=&quot;com.google.android.geo.API_KEY&quot;
            android:value=&quot;AIzaSyAciVULMAzZmraF4UZ8b1ldfHZ6xK3DNGs&quot; /&gt;

        &lt;activity android:name=&quot;.MainActivity&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;

&lt;/manifest&gt;
</pre>
<p>در قدم بعد یک Fragment در layout اکتیویتی به صورت زیر تعریف می‌کنم. قبلا در مطلب <a href="http://android-studio.ir/android-fragments/" target="_blank" rel="noopener noreferrer">آموزش کار با فرگمنت در اندروید</a> با Fragment آشنا شدیم.</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;fragment xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:id=&quot;@+id/map_fragment&quot;
    android:name=&quot;com.google.android.gms.maps.SupportMapFragment&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot; /&gt;
</pre>
<p>به ویژگی یا attribute هایی که برای فرگمنت تعریف شده دقت کنید. مقدار name باید همان چیزی باشد که در کد بالا مشاهده می‌کنید. همچنین در این اکتیویتی از Layout دیگری استفاده نشده بنابراین ویژگی context درون خود فرگمنت تعریف شده است.<br />
حالا نوبت کدهای اکتیویتی ست. ابتدا باید interface ای با نام OnMapReadyCallback را روی کلاس اکتیویتی implement کنم. اگر به نام این اینترفیس دقت کنید احتمالا کاربرد آنرا خواهید فهمید. یک CallBack (پاسخ) برای هنگامی که نقشه (Map) آماده (Ready) نمایش باشد. عملکرد آن به اینصورت است که بررسی می‌کند چنانچه Google Play Services روی دیوایس کاربر (موبایل، تبلت، گجت‌های پوشیدنی) نصب نباشد پیغامی نمایش داده می‌شود با این مضمون که برای اجرای این بخش از برنامه (یعنی نمایش نقشه) باید Google Play Services نصب شود. پس از نصب، کاربر به برنامه برمی‌گردد. اما در صورتی که Google Play Services از قبل روی دیوایس نصب شده باشد، متد و کدهای مربوط به نقشه را فراخوانی می‌کند.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/implement_OnMapReadyCallback_interface_on_activity.png" alt="مدیریت نقشه گوگل مپ توسط اینترفیس OnMapReadyCallback" /></p>
<p>متد این اینترفیس را باید به اکتیویتی اضافه کنیم. روی آن alt + enter می‌زنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/implement_OnMapReadyCallback_methods.png" alt="اضافه کردن متد موردنیاز OnMapReadyCallback به اکتیویتی" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/implement_onMapReady_method.png" alt="متد onMapReady" /></p>
<p>متد <span dir="ltr">onMapReady()</span> را انتخاب می‌کنم. اکتیویتی به صورت زیر تکمیل می‌شود:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.googlemap;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;

public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }


    @Override
    public void onMapReady(GoogleMap googleMap) {
        
    }
}
</pre>
<p>ابتدا متدهای <span dir="ltr">onCreate()</span> و <span dir="ltr">onMapReady()</span> را تکمیل کرده سپس توضیحات لازم را بیان می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.googlemap;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {

    private GoogleMap gMap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map_fragment);
        mapFragment.getMapAsync(this);

    }


    @Override
    public void onMapReady(GoogleMap googleMap) {

        gMap = googleMap;
        LatLng myLocation = new LatLng(34.016774, 58.168308);
        gMap.addMarker(new MarkerOptions().position(myLocation).title(&quot;فردوس&quot;)).snippet(&quot;خراسان جنوبی&quot;));
        gMap.moveCamera(CameraUpdateFactory.newLatLng(myLocation));

    }
}
</pre>
<p>برای نمایش نقشه Google Map در اندروید از SupportMapFragment استفاده می‌کنیم. یک نمونه (شیء) از این متد با نام mapFragment در <span dir="ltr">onCreate()</span> تعریف کردم. در خط بعد و توسط متد <span dir="ltr">getMapAsync()</span> زمانی که نقشه آماده نمایش باشد آنرا به mapFragment پاس می‌دهد. یعنی زمانی که OnMapReadyCallback اعلام کند نقشه آماده نمایش است.<br />
در متد <span dir="ltr">onMapReady()</span> جزئیات مربوط به نقشه تعریف شده است. در کلاس اکتیویتی ابتدا یک نمونه از کلاس GoogleMap با نام gMap و سپس در <span dir="ltr">onMapReady()</span> تعریف شده است. برای تعیین موقعیت مکانی مدنظر از کلاس LatLng استفاده می‌شود. یک نمونه از آن با نام myLocation تعریف کردم. این کلاس دو ورودی می‌گیرد. ورودی اول Latitude (عرض جغرافیایی) و دومی Longitude (طول جغرافیایی):</p>
<pre class="brush: java; title: ; notranslate">
LatLng myLocation = new LatLng(34.016774, 58.168308);
</pre>
<p>اگر با نقشه‌هایی مانند گوگل مپ کار کرده‌اید احتمالا این دو اصطلاح برایتان آشنا بنظر می‌رسد. برای تعیین یک موقعیت مکانی نیاز به طول و عرض جغرافیایی آن نقطه داریم. در نسخه وب Google Maps یا برنامه موبایلی Maps روی هر نقطه‌ای که کلیک کنید این دو عدد را نشان می‌دهد. اولی latitude و دومی longitude است. این دو عدد را به ترتیب در ورودی <span dir="ltr">LatLng()</span> قرار می‌دهم تا همان نقطه روی نقشه نشان داده شود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/get_latitude_longitude_on_google_map.jpg" alt="دریافت مختصات latitude و longitude روی نقشه گوگل" /></p>
<p>در خط بعد، از addMarker برای نمایش محل دقیق موقعیت مکانی استفاده شده. Marker یک نشانه است که روی نقشه، محل مدنظر را به کاربر نشان می‌دهد. همچنین می‌توان اطلاعات و جزئیاتی از آن نقطه را نیز به مارکر اضافه کرد:</p>
<pre class="brush: java; title: ; notranslate">
gMap.addMarker(new MarkerOptions().position(myLocation).title(&quot;فردوس&quot;)).snippet(&quot;خراسان جنوبی&quot;));
</pre>
<p>توسط <span dir="ltr">position()</span> موقعیت و <span dir="ltr">title()</span> و <span dir="ltr">snippet()</span> نام و توضیحات محل موردنظر تعیین می‌شود. موقعیت از myLocation گرفته شده.<br />
در نهایت با استفاده از <span dir="ltr">moveCamera()</span> قسمتی از نقشه روی صفحه نمایش قرار می‌گیرد که نقطه مدنظر ما در وسط آن (از هردو سمت افقی و عمودی) قرار دارد. در صورتی که این کلاس تعریف نشود، نقشه در محدوده پیش فرض گوگل مپ نمایش داده خواهد شد و کاربر برای پیدا کردن Marker باید به صورت دستی نقشه را جابجا کند.<br />
پروژه را اجرا می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/run_google_map_project_on_emulator.jpg" alt="اجرای پروژه نمایش نقشه Google Map اندروید استودیو" /></p>
<p>آیکون مارکر را مشاهده می‌کنید که درست در وسط صفحه قرار گرفته و نقطه‌ای که مختصات آن را تعیین کرده بودیم را نشان می‌دهد. اما چنانچه قصد داریم کاربر محل دقیق مدنظر را از فاصله‌ی نزدیکتر مشاهده کند لازم است نقشه در حالت زوم شده قرار گیرد:</p>
<pre class="brush: java; title: ; notranslate">
gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(myLocation, 15.5f));
</pre>
<p>بجای newLatLang از newLatLangZoom استفاده کردم. این متد علاوه بر گرفتن مختصات نقطه، یک عدد float (شناور) را می‌گیرد که میزان زوم نقشه را مشخص می‌کند. این عدد باید بین ۲٫۰ تا ۲۱٫۰ باشد. در خط بالا من مقدار ۱۵٫۵ تعیین کردم. اعداد float با اضافه شدن حرف f در انتهای عدد مشخص می‌شوند. پروژه را دوباره اجرا می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/zoom_on_map_using_newLatLngZoom_method.jpg" alt="استفاده از متد newLatLngZoom برای زوم روی نقشه گوگل" /></p>
<p>با کلیک یا ضربه روی آیکون مارکر، توضیحات محل نمایش داده می‌شود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/click_on_map_marker.jpg" alt="نمایش عنوان و توضیحات محل با کلیک روی Marker نقشه Google Maps" /></p>
<p>همچنین آیکون مارکر نیز قابل تغییر است:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/change_marker_icon.png" alt="تغییر آیکون Marker (مارکر) گوگل مپ در اندروید استودیو" /></p>
<pre class="brush: java; title: ; notranslate">
gMap.addMarker(new MarkerOptions().position(myLocation).title(&quot;فردوس&quot;).snippet(&quot;خراسان جنوبی&quot;).icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
</pre>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/change_marker_icon_2.jpg" alt="تغییر آیکون Marker (مارکر) گوگل مپ در اندروید استودیو" /></p>
<p>امکانات بسیار زیادی در API گوگل مپ در اختیار داریم اما بنا به دلایلی که در ابتدای مبحث ذکر شد و محدودیت‌هایی که پیش روی ماست، صرفا به تعداد محدودی از قابلیت‌های موجود در همین API نقشه Google Map در اندروید اشاره و از معرفی سایر API ها و امکانات آن شامل Geocoder و&#8230; صرف نظر می‌کنم.<br />
<b>۱: نوع نقشه:</b> همانطور که در نسخه تحت وب گوگل مپ یا اپلیکیشن آن، امکان نمایش نقشه در چند حالت مختلف (Normal، Satellite، Hybrid) وجود دارد، در API هم می‌توان هرکدام از این حالت‌ها را از گوگل دریافت و روی اپلیکیشن نمایش داد. اینکار توسط متد <span dir="ltr">setMapType()</span> انجام می‌شود:</p>
<pre class="brush: java; title: ; notranslate">
gMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
</pre>
<p>خط فوق را به متد <span dir="ltr">onMapReady()</span> اضافه کردم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/show_satellite_map_using_MAP_TYPE_SATELLITE.jpg" alt="نمایش نقشه ماهواره‌ای گوگل توسط MAP_TYPE_SATELLITE" /></p>
<p>با تعریف MAP_TYPE_SATELLITE نقشه در حالت تصاویر ماهواره‌ای لود می‌شود. واژه Satellite به معنی ماهواره است. سایر آیتم‌ها را در لیست زیر مشاهده می‌کنید:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/setMapType_items.jpg" alt="آیتم‌های setMapType برای نمایش حالت‌های مختلف نقشه گوگل" /></p>
<p>MAP_TYPE_NORMAL : همان نقشه پیش فرض را نمایش می‌دهد که شامل نام خیابانها، اماکن و&#8230; هست:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/show_normal_map_using_MAP_TYPE_NORMAL.jpg" alt="نمایش نقشه نرمال توسط MAP_TYPE_NORMAL" /></p>
<p>MAP_TYPE_TERRAIN: داده‌های توپوگرافی را نمایش می‌دهد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/show_topography_map_using_MAP_TYPE_TERRAIN.jpg" alt="نمایش نقشه Terrain توپوگرافی توسط MAP_TYPE_TERRAIN" /></p>
<p>MAP_TYPE_HYBRID: ترکیبی از حالت‌های NORMAL و SATELLITE است به این صورت که نام خیابانها، اماکن و سایر جزئیات بر روی تصاویر ماهواره‌ای نمایش داده می‌شود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/show_hybrid_map_using_MAP_TYPE_HYBRID.jpg" alt="نمایش نقشه هیبرید گوگل توسط MAP_TYPE_HYBRID" /></p>
<p>Map_TYPE_NONE: همان نقشه NORMAL را نمایش می‌دهد اما با جزئیاتی کمتر:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/show_map_on_Map_TYPE_NONE_mode.jpg" alt="نمایش نقشه ساده گوگل توسط Map_TYPE_NONE" /></p>
<p><b>۲: دکمه‌های زوم:</b> توسط متد <span dir="ltr">setZoomControlsEnabled()</span> می‌توانیم دکمه‌های +/- را به نقشه اضافه کنیم که در اینصورت کاربر امکان کنترل میزان زوم نقشه را دارد:</p>
<pre class="brush: java; title: ; notranslate">
gMap.getUiSettings().setZoomControlsEnabled(true);
</pre>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/setZoomControlsEnabled_method.jpg" alt="اضافه کردن دکمه‌های +/- نقشه گوگل توسط متد setZoomControlsEnabled" /></p>
<p>همچنین با تعریف کردن متد <span dir="ltr">setZoomGesturesEnabled()</span> علاوه بر دکمه‌های +/- عمل افزایش و کاهش زوم به ترتیب توسط &#8220;دو ضربه&#8221; روی صفحه نمایش و &#8220;یک ضربه با دو انگشت&#8221; روی صفحه نمایش انجام خواهد شد.<br />
کد نهایی اکتیویتی:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.googlemap;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {

    private GoogleMap gMap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map_fragment);
        mapFragment.getMapAsync(this);

    }


    @Override
    public void onMapReady(GoogleMap googleMap) {

        gMap = googleMap;
        gMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
        LatLng myLocation = new LatLng(34.016774, 58.168308);
        gMap.addMarker(new MarkerOptions().position(myLocation).title(&quot;فردوس&quot;).snippet(&quot;خراسان جنوبی&quot;)
                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
        //gMap.moveCamera(CameraUpdateFactory.newLatLng(myLocation));
        gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(myLocation, 15.5f));
        gMap.getUiSettings().setZoomControlsEnabled(true);
        gMap.getUiSettings().setZoomGesturesEnabled(true);

    }
}
</pre>
<h2>ساخت API Key جدید برای اپ release شده</h2>
<p>کلیدی که ابتدای کار در کنسول ایجاد کردیم صرفا در حالت دیباگ پروژه از طرف گوگل تایید می‌شود و برای ساخت (release) کردن اپلیکیشن لازم است SHA-1 certificate fingerprint مربوط به امضای برنامه (Key store) را به همراه نام پکیج برنامه در کنسول تعریف کنیم. در مبحث <a href="http://android-studio.ir/generating-signed-apk/" target="_blank" rel="noopener noreferrer">آموزش ساخت فایل APK</a> با نحوه ایجاد فایل امضاء آشنا شدیم.<br />
برای بدست آوردن کد SHA-1 از ابزار keytool در JDK استفاده می‌کنیم. تفاوتی نمی‌کند خودتان یک نسخه از JDK را روی سیستم عامل نصب کرده‌اید یا از JDK داخلی و پیش فرض اندروید استودیو استفاده می‌کنید. در هرصورت در محل نصب JDK یک پوشه با نام bin وجود دارد که ابزار ازجمله keytool درون آن قرار داده شده. اگر JDK کاستوم نصب نکرده‌اید، نسخه داخلی اندروید استودیو در محل نصب اندروید استودیو و در پوشه jre قرار دارد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/android_studio_jdk_location.png" alt="محل نصب JDK اندروید استودیو" /></p>
<p>Keytool درون پوشه bin پوشه jre قرار دارد بنابراین در سیستم عامل من آدرس این پوشه به اینصورت است:</p>
<pre class="brush: plain; title: ; notranslate">
C:\Program Files\Android\Android Studio1\jre\bin
</pre>
<p>داخل این پوشه با کلیدهای shift + right click گزینه open commant prompt (در ویندوز) را انتخاب می‌کنم تا cmd در این فولدر اجرا شود. یا اگر این گزینه در ویندوز فعال نیست، از منوی استارت cmd را اجرا کرده و با دستور زیر به این فولدر می‌روم:</p>
<pre class="brush: plain; title: ; notranslate">
cd C:\Program Files\Android\Android Studio1\jre\bin
</pre>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/get_SHA-1_using_keytool.png" alt="دریافت SHA-1 کلید امضاء توسط ابزار keytool" /></p>
<p>سپس دستور زیر را اجرا می‌کنم:</p>
<pre class="brush: plain; title: ; notranslate">
keytool -list -v -keystore &quot;KEYSTORE_PATH&quot;
</pre>
<p>در KEYSTORE_PATH محلی که فایل کلید امضاء (<span dir="ltr">.jks</span>) قرار گرفته را جایگزین می‌کنم. برای مثال:</p>
<pre class="brush: plain; title: ; notranslate">
keytool -list -v -keystore &quot;C:\MyKey.jks&quot;
</pre>
<p>با اجرای دستور فوق، رمزی که هنگام ساخت امضاء تعیین کرده بودم دریافت می‌شود که آنرا وارد کرده و enter می‌زنم. دقت کنید هنگام تایپ رمز عبور در cmd کاراکترها دیده نمی‌شود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/get_SHA-1_using_keytool_2.png" alt="دریافت SHA-1 کلید امضاء توسط ابزار keytool" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/get_SHA-1_using_keytool_3.png" alt="دریافت SHA-1 کلید امضاء توسط ابزار keytool" /></p>
<p>مشاهده می‌کنید اطلاعات کلید ازجمله SHA1 در خط فرمان چاپ شد.<br />
حالا وارد کنسول گوگل شده و در قسمت Credentials کلیدی که قبلا ساختم را ویرایش می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/googlemap/add_SHA1_to_api_key_in_console.png" alt="ثبت کد SHA-1 certificate fingerprint در کنسول دولوپر گوگل" /></p>
<p>در قسمت Application restrictions گزینه Android apps را انتخاب کرده و سپس روی ADD AN ITEM در قسمت Restrict usage to your Android apps کلیک کردم. در مرحله بعد دو فیلد Package name و SHA-1 certificate fingerprint را تکمیل می‌کنم. در نهایت تغییرات را باید SAVE کنم.<br />
با انجام این تغییرات، اپلیکیشن release شده امکان استفاده از این API را خواهد داشت.</p>
<p>نکته: دستورات keytool در سیستم عامل‌های Linux و MAC یک تفاوت جزئی دارد. لطفا در وب جستجو کنید.</p>
<p>موفق و پیروز باشید.</p>
<h3>مطالعه‌ی بیشتر:</h3>
<p style="text-align: left; direction: ltr;">
<a href="https://developers.google.com/android/reference/com/google/android/gms/maps/SupportMapFragment" target="_blank" rel="noopener noreferrer">https://developers.google.com/android/reference/com/google/android/gms/maps/SupportMapFragment</a><br />
<a href="https://developers.google.com/android/reference/com/google/android/gms/maps/OnMapReadyCallback" target="_blank" rel="noopener noreferrer">https://developers.google.com/android/reference/com/google/android/gms/maps/OnMapReadyCallback</a><br />
<a href="https://developers.google.com/android/reference/com/google/android/gms/maps/MapFragment.html" target="_blank" rel="noopener noreferrer">https://developers.google.com/android/reference/com/google/android/gms/maps/MapFragment.html</a><br />
<a href="https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.html" target="_blank" rel="noopener noreferrer">https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.html</a><br />
<a href="https://developers.google.com/android/reference/com/google/android/gms/maps/model/LatLng" target="_blank" rel="noopener noreferrer">https://developers.google.com/android/reference/com/google/android/gms/maps/model/LatLng</a><br />
<a href="https://developers.google.com/android/reference/com/google/android/gms/maps/CameraUpdateFactory.html" target="_blank" rel="noopener noreferrer">https://developers.google.com/android/reference/com/google/android/gms/maps/CameraUpdateFactory.html</a>
</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۳۲<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۳ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/13_1_GoogleMap.zip" class="button green edd-submit">دانلود رایگان با حجم ۳ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/13_1_GoogleMap.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/add-google-map-to-android-app/">نمایش نقشه Google Map در اندروید</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/add-google-map-to-android-app/feed/</wfw:commentRss>
			<slash:comments>24</slash:comments>
		
		
			</item>
		<item>
		<title>کار با دوربین در اندروید توسط Camera۲ API</title>
		<link>https://android-studio.ir/work-with-android-camera2-api/</link>
					<comments>https://android-studio.ir/work-with-android-camera2-api/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Sun, 10 Nov 2019 13:04:40 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=166255</guid>

					<description><![CDATA[<p>در این قسمت از سری مباحث برنامه نویسی اندروید به نحوه کار با Camera2 API جهت اتصال مستقیم به سخت افزار دوربین اندروید و ذخیره تصویر روی حافظه دیوایس می‌پردازیم. آنچه در این آموزش می‌خوانید: معرفی Camera2 API اندروید معرفی Camera API بررسی مزایای Camera2 API نسبت به آن مجوز یا Permission های موردنیاز برای [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/work-with-android-camera2-api/">کار با دوربین در اندروید توسط Camera۲ API</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/android_camera2_api_tutorial.png" alt="آموزش گرفتن عکس در برنامه نویسی اندروید توسط Camera2 API" /></p>
<p>در این قسمت از سری مباحث <a href="http://android-studio.ir/" target="_blank" rel="noopener noreferrer">برنامه نویسی اندروید</a> به نحوه کار با Camera2 API جهت اتصال مستقیم به سخت افزار دوربین اندروید و ذخیره تصویر روی حافظه دیوایس می‌پردازیم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید:</b></p>
<ul>
<li>معرفی Camera2 API اندروید</li>
<li>معرفی Camera API بررسی مزایای Camera2 API نسبت به آن</li>
<li>مجوز یا Permission های موردنیاز برای اتصال به دوربین و ذخیره عکس روی حافظه</li>
<li>استفاده از تگ uses-feature جهت فیلتر کردن برنامه در فروشگاه Google Play</li>
<li>آشنایی با ویجت TextureView جهت نمایش خروجی دوربین در اکتیویتی</li>
<li>معرفی متد شنونده TextureView.SurfaceTextureListener </li>
<li>معرفی کلاس CameraManager و نحوه کار با آن جهت مدیریت دوربین‌ها</li>
<li>استفاده از متد getCameraIdList برای دریافت لیست شناسه (ID) دوربین‌ها</li>
<li>استفاده از متد CameraCharacteristics برای دریافت مشخصات دوربین‌ها</li>
<li>کار با متد StreamConfigurationMap جهت دریافت محتوای استریم شده از دوربین</li>
<li>دریافت سایز خروجی دوربین بوسیله متد getOutputSizes</li>
<li>معرفی اولیه قابلیت امنیتی Runtime Permission برای دریافت مجوز در اندروید ۶ و به بالا</li>
<li>باز کردن دوربین توسط متد openCamera</li>
<li>آشنایی با CameraAccessException و نحوه مدیریت (Handle) آن</li>
<li>معرفی کاربرد متد CameraDevice</li>
<li>معرفی کاربرد متد setDefaultBufferSize</li>
<li>استفاده از متد CaptureRequest.Builder جهت درخواست گرفتن عکس</li>
<li>دسترسی به داده‌های تصویر رندر شده توسط ImageReader</li>
<li>استفاده از تاریخ ثبت تصویر به عنوان نام فایل توسط SimpleDateFormat</li>
<li>تعیین محل ذخیره فایل بوسیله متد Environment.getExternalStorageDirectory</li>
<li>ذخیره محتوای استریم شده از دوربین بواسطه OutputStream</li>
<li>کار با متدهای OnImageAvailableListener و setOnImageAvailableListener</li>
</ul>
</div>
<p class="copy">این جلسه در قالب PDF و در ۵۰ صفحه تهیه شده که در ادامه چند صفحه‌ ابتدایی را مشاهده می‌کنید:</p>
<p>
در جلسه قبل (<a href="http://android-studio.ir/working-with-camera-in-android-programming/" target="_blank" rel="noopener noreferrer">آموزش کار با دوربین در برنامه نویسی اندروید</a>) به نحوه کار با دوربین و گرفتن عکس از طریق برنامه داخلی مانند برنامه پیش فرض دوربین پرداختیم. به صورتی که کاربر برای استفاده از دوربین و ثبت تصویر، ابتدا به یک برنامه مدیریت دوربین منتقل شده و سپس نتیجه آن به اپلیکیشن اصلی برگردانده می‌شد.<br />
در این قسمت از مباحث آموزش برنامه نویسی اندروید به آموزش کار با دوربین توسط Camera2 API می‌پردازیم. با بکارگیری API دوربین سیستم عامل اندروید، اتصال به دوربین دیوایس اندرویدی به صورت مستقیم و بدون نیاز به برنامه جانبی امکان پذیر بوده و کاربر برای ثبت تصویر یا ویدئو از محیط برنامه اصلی خارج نمی‌گردد.</p>
<h2>معرفی Camera2 API اندروید</h2>
<p>در گذشته و تا قبل از معرفی اندروید ۵ یعنی Lollipop یا به عبارتی API 21، توسعه دهندگان اندروید برای ارتباط با دوربین دیوایس‌های اندرویدی در برنامه خود از Camera API استفاده می‌کردند. همزمان با معرفی اندروید ۵ در سال ۲۰۱۴، گوگل از API جدید خود به جهت ارتباط با دوربین رونمایی کرد که Camera2 API نام دارد.<br />
Camera API در حال حاضر Deprecate (منقضی) شده و حتی در نسخه‌های اخیر اندروید به درستی پشتیبانی نمی‌شود. بنابراین لازم است از نسل جدید آن یعنی Camera2 API در پروژه‌های اندرویدی خود استفاده کنیم.</p>
<h2>مزایای Camera2 API نسبت به Camera API</h2>
<p>نسخه جدید این API مزایایی نسبت به نسل قبل دارد که به تعدادی از مهمترین آنها اشاره می‌کنم:</p>
<ul class="content-list">
<li>با سخت افزار جدید سازگاری بهتری داشته و در نتیجه شاهد خروجی بهینه‌تر و با کیفیت بالاتری هستیم.</li>
<li>ثبت تصاویر با سرعت بالاتری انجام می‌شود.</li>
<li>تعدادی افکت (Effect) و فیلتر (Filter) را به صورت مستقیم روی دوربین می‌توان اعمال کرد.</li>
<li>بدست آوردن لیست دوربین‌های موجود توسط متد getCameraIdList.</li>
<li>بدست آوردن مشخصات، ویژگی‌ها و تنظیمات (ازجمله رزولوشن) هریک از دوربین‌های موجود توسط متد CameraCharacteristics و استفاده از آنها بر اساس مشخصات دریافتی، جهت حصول بهترین نتیجه. به عبارتی در این نسخه، کنترل بهتری روی دوربین‌ها داریم.</li>
</ul>
<h2>پروژه کار با دوربین و ذخیره عکس روی حافظه دیوایس اندرویدی</h2>
<p>در این قسمت، پروژه‌ای را تمرین و بررسی می‌کنیم که در ساده‌ترین حالت ممکن، اَپ ما به صورت مستقیم و از طریق Camera2 API با دوربین گوشی یا تبلت اندرویدی ارتباط برقرار کرده و با زدن یک دکمه، تصویر موردنظر روی حافظه داخلی دیوایس ذخیره می‌گردد. سایر امکانات موجود در API را در آموزش‌های آتی و همچنین در قالب پروژه‌های کاربردی به صورت کاملتر بررسی خواهیم کرد.<br />
یک <a href="http://android-studio.ir/create-android-project-and-its-structure/" target="_blank" rel="noopener noreferrer">پروژه جدید اندرویدی</a> با نام Camera2 API در اندروید استودیو با یک Empty Activity و Minimum API 21 ایجاد می‌کنم. من با زبان جاوا کار می‌کنم بنابراین در قسمت زبان، گزینه Java را انتخاب می‌کنم.<br />
در اولین قدم باید Permission یا مجوزهای موردنیاز برای اپلیکیشن اندرویدی خود را در مانیفست پروژه تعریف کنیم. برخلاف پروژه قسمت قبل که برای ثبت تصویر از یک برنامه واسط استفاده کردیم و نیازی به تعیین مجوز نبود، در این پروژه ما به طور مستقیم و توسط API با سخت افزار دوربین سروکار داریم بنابراین لازم است ابتدا مجوز مربوطه از کاربر گرفته شود، در غیر اینصورت سیستم عامل اندروید حق دسترسی به دوربین و کارت حافظه را در اختیار برنامه ما قرار نخواهد داد.</p>
<pre class="brush: xml; title: ; notranslate">
&lt;uses-permission android:name=&quot;android.permission.CAMERA&quot; /&gt;
&lt;uses-permission android:name=&quot;android.hardware.camera2.full&quot; /&gt;
&lt;uses-permission android:name=&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot; /&gt;
</pre>
<p>سه مجوز فوق را به مانیفست پروژه اضافه کردم. دو مورد نخست جهت دسترسی به دوربین (CAMERA) و مورد سوم مربوط به دریافت مجوز دسترسی به حافظه دیوایس جهت ذخیره سازی تصویر دریافتی از دوربین می‌باشد. WRITE EXTERNAL STORAGE به معنی &#8220;نوشتن روی حافظه ذخیره سازی&#8221; است.<br />
قبلا در مطلب مربوط به <a href="http://android-studio.ir/uses-feature-element-in-android-project-manifest/" target="_blank" rel="noopener noreferrer">معرفی تگ uses-feature در اندروید</a> پرداختیم. گفتیم از این تگ برای فیلتر اپلیکیشن‌ها در فروشگاه Google Play استفاده می‌شود. فرض را بر این می‌گذاریم که کار با دوربین جزء قابلیت‌های اصلی برنامه ماست و بدون آن، عملکردش با اختلال مواجه می‌گردد. بنابراین برنامه ما در فروشگاه گوگل پلی فقط به کاربرانی باید نمایش داده شود که دیوایس‌شان دارای حداقل یک دوربین باشد. در غیر اینصورت و بعد از نصب برنامه، امکان بهره مندی از اپ ما را نخواهند داشت. لذا تگ زیر را نیز به مانیفست اضافه می‌کنم:<br />
<uses-feature android:name="android.hardware.camera" android:required="true" /></p>
<p>فایل نهایی AndroidManifest.xml:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.camera2api&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.CAMERA&quot; /&gt;
    &lt;uses-permission android:name=&quot;android.hardware.camera2.full&quot; /&gt;
    &lt;uses-permission android:name=&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot; /&gt;
    &lt;uses-feature android:name=&quot;android.hardware.camera&quot; android:required=&quot;true&quot; /&gt;
    
    &lt;application
        android:allowBackup=&quot;true&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/AppTheme&quot;&gt;
        &lt;activity android:name=&quot;.MainActivity&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;

&lt;/manifest&gt;
</pre>
<p>سپس layout اکتیویتی را به صورتی ویرایش می‌کنم که شامل یک View برای نمایش دوربین و یک Button برای ثبت تصویر باشد. برای نمایش محتوای دریافتی از دوربین یا به اصطلاح Camera Preview از TextureView استفاده می‌کنیم. از TextureView برای نمایش محتواهایی مانند ویدئو، ویدئوی زنده (Stream) و سایر موارد مشابه بکار می‌رود که در اینجا برای نمایش خروجی دوربین از آن استفاده می‌کنیم:</p>
<p>activity_main.xml:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;RelativeLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;TextureView
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_alignParentTop=&quot;true&quot;
        android:layout_alignParentStart=&quot;true&quot;
        android:id=&quot;@+id/texture_view&quot;/&gt;

    &lt;Button
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_alignParentBottom=&quot;true&quot;
        android:layout_centerHorizontal=&quot;true&quot;
        android:id=&quot;@+id/capture_btn&quot;
        android:text=&quot;Capture&quot; /&gt;

&lt;/RelativeLayout&gt;
</pre>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/add_textureview_widget_to_android_camera_project.png" alt="اضافه کردن ویجت TextureView به اکتیویتی در اندروید" /></p>
<p>در این پروژه من به تولبار نیازی ندارم و ترجیح می‌دهم آنرا غیر فعال کنم تا TextureView بخش بیشتری از صفحه نمایش را اشغال کند. بنابراین استایل تم پیش فرض را به NoActionBar تغییر می‌دهم:<br />
styles.xml:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;style name=&quot;AppTheme&quot; parent=&quot;Theme.AppCompat.Light.NoActionBar&quot;&gt;
    &lt;item name=&quot;colorPrimary&quot;&gt;@color/colorPrimary&lt;/item&gt;
    &lt;item name=&quot;colorPrimaryDark&quot;&gt;@color/colorPrimaryDark&lt;/item&gt;
    &lt;item name=&quot;colorAccent&quot;&gt;@color/colorAccent&lt;/item&gt;
&lt;/style&gt;
</pre>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/disable_material_actionbar.png" alt="غیر فعال کردن تولبار تم متریال اندروید توسط NoActionBar" /></p>
<p>خب! حالا نوبت به اکتیویتی می‌رسد. ابتدا متدهای موردنیاز را درون بدنه اصلی کلاس MainActivity تعریف می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.camera2api;

import androidx.appcompat.app.AppCompatActivity;

import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.widget.Button;

import java.io.File;

public class MainActivity extends AppCompatActivity {

    private TextureView mTextureView;
    private Button captureButton;
    private static final SparseIntArray ORIENTATION = new SparseIntArray();

    static {
        ORIENTATION.append(Surface.ROTATION_0, 90);
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    }

    private String cameraId;
    private CameraDevice camDevice;
    private CameraCaptureSession camCapSession;
    private CaptureRequest capRequest;
    private CaptureRequest.Builder capRequestBuilder;
    private Size imgDimentions;
    private ImageReader imgReader;
    private File picFile;
    private Handler backgroundHandler;
    private HandlerThread backgroundThread;
    private static final int camPermissionReqCode = 200;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextureView = findViewById(R.id.texture_view);
        captureButton = findViewById(R.id.capture_btn);

    }
}
</pre>
<p>نترسید! خط به خط کدها را در ادامه آموزش توضیح می‌دهم.<br />
ابتدا دو ویجت TextureView و Button که در layout قرار دارند را درون متد onCreate تعریف کرده‌ام.<br />
برای برقراری ارتباط بین دوربین و TextureView نیاز به یک شنونده (Listener) دارم. درون بدنه اصلی کلاس اکتیویتی یک متد از نوع TextureView.SurfaceTextureListener با نام دلخواه textureListener اضافه می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/create_SurfaceTextureListener_method.png" alt="متد SurfaceTextureListener در Camera2 API" /></p>
<p>متد به صورت زیر توسط اندروید استودیو ساخته می‌شود:</p>
<pre class="brush: java; title: ; notranslate">
TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

    }
};
</pre>
<p>این متد ۴ تابع با نام‌های onSurfaceTextureAvailable، onSurfaceTextureSizeChanged، onSurfaceTextureDestroyed و onSurfaceTextureUpdated دارد که کاربرد هرکدام از آنها از نحوه نامگذاری مشخص می‌شود. واژه Available در متد onSurfaceTextureAvailable به معنی &#8220;در دسترس&#8221; است لذا این متد زمانی اجرا خواهد شد که TextureView در حال اجرا باشد. یعنی زمانی که اکتیویتی حاوی TextureView مدنظر ما اجرا شد، این متد صدا زده می‌شود.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> اگر بعد از اضافه کردن متد TextureView.SurfaceTextureListener و یا سایر مواقع با اروری مواجه شدید، نشانگر را روی محل ارور ببرید تا دلیل خطا را مشاهده کنید:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/expected_error.png" alt="رفع خطاهای اندروید استودیو ازجمله expected" /></p>
<p>در این مورد صرفا لازم است یک “;” به انتهای بلاک متد اضافه شود.
</p></div>
<p>گفتیم با اجرای اکتیویتی و در دسترس قرار گرفتن TextureView متد onSurfaceTextureAvailable هم اجرا می‌شود. بنابراین در این لحظه لازم است دوربین فعال شده و محتوای دریافتی از آن (همان پیش نمایش) به آن ارسال گردد تا کاربر خروجی دوربین را مشاهده نماید. لذا یک متد با نام دلخواه openTheCamera به این تابع اضافه کرده و کدهای مربوط به دریافت اطلاعات دوربین را درون آن می‌نویسم:</p>
<pre class="brush: java; title: ; notranslate">
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
    openTheCamera();
}
</pre>
<p>برای ساخت متد بجای نوشتن دستی آن، کافیست در حالی که نشانگر موس روی نام این تابع هست دکمه‌های alt + Enter را زده و روی گزینه create method کلیک کنیم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/create_openCamera_method_in_android_studio.png" alt="استفاده از متد openCamera در برنامه نویسی اندروید برای باز کردن دوربین" /></p>
<p>در مرحله بعد روی گزینه همنام با اکتیویتی یعنی MainActivity را انتخاب می‌کنم تا متد درون بدنه اصلی کلاس ساخته شود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/create_openCamera_method_in_android_studio_2.png" alt="استفاده از متد openCamera در Camera2 API برای باز کردن دوربین" /></p>
<p>به اینصورت متد openTheCamera درون کلاس اکتیویتی و بعد از متد TextureView.SurfaceTextureListener اضافه می‌شود:</p>
<pre class="brush: java; title: ; notranslate">
TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        openTheCamera();
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

    }
};

private void openTheCamera() {
}
</pre>
<p>قبل از ادامه کار کد کامل اکتیویتی را قرار می‌دهم تا درک بهتری از وضعیت فعلی اکتیویتی داشته باشید:</p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.camera2api;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;

import java.io.File;

public class MainActivity extends AppCompatActivity {

    private TextureView mTextureView;
    private Button captureButton;
    private static final SparseIntArray ORIENTATION = new SparseIntArray();

    static {
        ORIENTATION.append(Surface.ROTATION_0, 90);
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    }

    private String cameraId;
    private CameraDevice camDevice;
    private CameraCaptureSession camCapSession;
    private CaptureRequest capRequest;
    private CaptureRequest.Builder capRequestBuilder;
    private Size imgDimentions;
    private ImageReader imgReader;
    private File picFile;
    private Handler backgroundHandler;
    private HandlerThread backgroundThread;
    private static final int camPermissionReqCode = 200;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextureView = findViewById(R.id.texture_view);
        captureButton = findViewById(R.id.capture_btn);

    }

    TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
            openTheCamera();
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

        }
    };

    private void openTheCamera() {
    }

}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> این آموزش را با دقت و حوصله بیشتری دنبال کنید. همه آموزش‌ها به همراه سورس منتشر شده بنابراین هرجا دچار ابهام شدید سورس را بررسی کنید. برای مشاهده سورس نیازی نیست حتما آنرا داخل اندروید استودیو Import کنید. کافیست فایل‌های پروژه را با یک ویرایشگر ساده مانند ++Notepad باز کنید.
</div>
<p>متد openTheCamera را به صورت زیر تکمیل کردم:</p>
<pre class="brush: java; title: ; notranslate">
private void openTheCamera() {

    CameraManager camManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    cameraId = camManager.getCameraIdList() &#x5B;0];
    CameraCharacteristics camCharacteristics = camManager.getCameraCharacteristics(cameraId);
    StreamConfigurationMap streamMap = camCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    imgDimentions = streamMap.getOutputSizes(SurfaceTexture.class) &#x5B;0];

    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
            &amp;&amp; ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(MainActivity.this, new String&#x5B;] {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, camPermissionReqCode);
        return;

    }

    camManager.openCamera(cameraId, camStateCallback, null);

}
</pre>
<p>در خط اول یک شیء از کلاس CameraManager با نام camManager ساختم. این کلاس برای مدیریت دوربین‌ها استفاده می‌گردد. در خط بعد متد getCameraIdList را مشاهده می‌کنید. توسط این متد، لیست شناسه (ID) دوربین(های) موجود در دیوایس برگردانده می‌شود. من می‌خواهم صرفا از دوربین اصلی استفاده شود بنابراین مقدار [۰] را تعریف کرده‌ام. بنابراین دوربین اصلی در متغیر cameraId قرار می‌گیرد.<br />
در ابتدای مبحث عرض کردم در Camera2 API می‌توان مشخصات و جزئیات مربوط به هر دوربین را دریافت کرد. در ادامه و با استفاده از کلاس CameraCharacteristics مربوط به API، مشخصات دوربین اصلی یعنی دوربینی که شناسه آن در cameraId ذخیره شده در camCharacteristics قرار می‌گیرد.<br />
در ادامه به StreamConfigurationMap نیاز داریم تا بوسیله آن بتوان محتوا یا فریم‌های دریافتی از دوربین (به عبارتی محتوای استریم شده از دوربین) را جهت ثبت عکس موردنظر ذخیره کرد.<br />
ما باید اندازه تصویر دریافتی از دوربین را هم داشته باشیم. در خط بعد توسط getOutputSizes سایز خروجی دوربین اصلی (۰) دریافت شده و در متغیر imgDimentions که از نوع Size تعریف شده بود ذخیره می‌گردد.<br />
از اندروید Marshmallow یا همان اندروید ۶ (API 21) به بعد، یک قابلیت امنیتی به نام Runtime Permission اضافه شد که دسترسی به قابلیت‌هایی مانند دوربین، خواندن و نوشتن روی کارت حافظه و&#8230; را به وسیله متد requestPermissions هنگام اجرای اپلیکیشن از کاربر می‌گیرد و شخص حق انتخاب و تایید یا عدم تایید هرکدام از موارد را به صورت جداگانه دارد. بر خلاف روال گذشته که تمامی مجوزها قبل از نصب اپ نمایش داده می‌شد و کاربر یا ملزم به پذیرش همه موارد بود تا بتواند فرایند نصب برنامه را ادامه دهد یا باید به کلی از نصب برنامه منصرف می‌شد. این قابلیت به طور مفصل در مطلب <a href="http://android-studio.ir/android-runtime-permission/">آموزش Runtime Permission در اندروید</a> معرفی شده است.</p>
<p>در نهایت با استفاده از متد openCamera ارتباط با دوربین مدنظر برقرار می‌گردد. این متد سه پارامتر دارد. مورد اول شناسه دوربین که آنرا در cameraId ذخیره کردم، مورد دوم یک StateCallBack جهت دریافت وضعیت دوربین و مورد سوم یک کنترل کننده است که در اینجا مقدار null گرفته.<br />
صرفا با توجه به نحوه نامگذاری متدها و کلاس‌ها، تا حدود زیادی نوع کاربرد مشخص می‌شود. state به معنی &#8220;وضعیت&#8221; و callback به معنی &#8220;پاسخ به تماس&#8221; است.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/openCamera_state_callback_parameter.png" alt="پارامتر StateCallBack متد openCamera" /></p>
<p>بنابراین می‌توان نتیجه گرفت که این متد قرار است وضعیت فعلی دوربین را برگرداند.<br />
برای پارامتر دوم نام دلخواه camStateCallback را تعریف کردم و در ادامه، متد مربوط به آن را به اکتیویتی اضافه خواهم کرد.<br />
در حال حاضر در قسمت getCameraIdList یک ارور وجود دارد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/CameraAccessException_error.png" alt="هندل کردن ارور CameraAccessException" /></p>
<p>در اینجا یک استثناء کنترل نشده (Unhandled exception) داریم. این استثناء شامل دو حالت است:<br />
۱-	هنگامی که CameraManager نتواند دوربین را باز کند.<br />
۲-	هنگامی که ارتباط بین برنامه و دوربین به هردلیل قطع شده و ادامه اتصال ممکن نیست.<br />
برای مدیریت یا به اصطلاح Handle کردن این استثناء، نشانگر موس را روی getCameraIdList قرار داده و کلیدهای alt + Enter را می‌زنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/add_exception_to_method_signature.png" alt="اضافه شدن throws CameraAccessException به متد openCamera" /></p>
<p>دو راه برای هندل کردن این استثناء وجود دارد. با انتخاب گزینه اول یعنی Add exception to method signature استثناء CameraAccessException در عنوان متد تعریف می‌شود:<br />
private void openTheCamera() throws CameraAccessException {}</p>
<p>یا اینکه با انتخاب گزینه دوم و قراردادن getCameraIdList در یک try catch نیز می‌توان مدیریت استثناء احتمالی را انجام داد. من گزینه اول را انتخاب می‌کنم.<br />
این ارور در متد onSurfaceTextureAvailable که openTheCamera را تعریف کردم نیز وجود دارد. مشابه قسمت قبل، با کلیدهای alt + Enter و انتخاب تنها گزینه موجود یعنی Surround with try/catch، متد را به صورت خودکار و بدون نیاز به نوشتن دستی درون یک دستور try catch قرار می‌دهم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/CameraAccessException_error_openCamera.png" alt="رفع ارور CameraAccessException توسط قرار دادن متد درون try catch" /></p>
<p>نتیجه:</p>
<pre class="brush: java; title: ; notranslate">
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
    try {
        openTheCamera();
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}
</pre>
<p>حالا نوبت ساخت پارامتر دوم openCamera یعنی camStateCallback است. یک متد با همین نام و از جنس CameraDevice.StateCallback قبل از متد openTheCamera به کلاس اکتیویتی اضافه می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/add_cameraDevice_stateCallBack_to_activity.png" alt="متد CameraDevice.StateCallback در Camera2 API دوربین اندروید" /></p>
<p>کلاس CameraDevice برای پردازشِ عکسِ گرفته شده استفاده می‌گردد.<br />
متد به اینصورت ساخته می‌شود:</p>
<pre class="brush: java; title: ; notranslate">
private final CameraDevice.StateCallback camStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {

    }

    @Override
    public void onDisconnected(@NonNull CameraDevice cameraDevice) {

    }

    @Override
    public void onError(@NonNull CameraDevice cameraDevice, int i) {

    }
};
</pre>
<p>به نامگذاری توابع درون این متد دقت کنید. در اینجا سه وضعیت دوربین را باید مدیریت کنیم:</p>
<ul class="content-list">
<li>onOpened: هنگامی که اتصال برقرار بوده و دوربین باز است.</li>
<li>onDisconnected: هنگامی که ارتباط با دوربین قطع شده.</li>
<li>onError: هنگامی که اروری دریافت می‌شود.</li>
</ul>
<p>
camStateCallBack را به صورت زیر تکمیل می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
private final CameraDevice.StateCallback camStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {
        camDevice = cameraDevice;
        createCameraPreview();
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice cameraDevice) {
        camDevice.close();
    }

    @Override
    public void onError(@NonNull CameraDevice cameraDevice, int i) {
        camDevice.close();
        camDevice = null;
    }
};
</pre>
<p>متد onOpened یک پارامتر از جنس CameraDevice و با نام cameraDevice دارد. camDevice ای که قبلا در اکتیویتی تعریف کردم را برابر این پارامتر قرار دادم. برای ساخت پیش نمایش دوربین و انتقال آن به TextureView نیاز به چند خط کد دیگر داریم. بنابراین یک متد به onOpened اضافه می‌کنم که نام آن را createCameraPreview انتخاب کرده‌ام.<br />
برای onDisconnected و onError توسط متد close ارتباط با دوربین را کامل قطع کردم.<br />
برای ساخت متد createCameraPreview مانند قبل روی آن alt + Enter، انتخاب گزینه create method… و در مرحله بعد انتخاب گزینه MainActivity تا متد درون کلاس ایجاد شود:</p>
<pre class="brush: java; title: ; notranslate">
private void createCameraPreview() {}
</pre>
<p>متد را به اینصورت کامل می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
private void createCameraPreview() {

    SurfaceTexture surTexure = mTextureView.getSurfaceTexture();
    surTexure.setDefaultBufferSize(imgDimentions.getWidth(), imgDimentions.getHeight());
    Surface mSurface = new Surface(surTexure);
    capRequestBuilder = camDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    capRequestBuilder.addTarget(mSurface);

    camDevice.createCaptureSession(Arrays.asList(mSurface), new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {

            if (camDevice == null) {
                return;
            }

            camCapSession = cameraCaptureSession;
            updatePreview();

        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {

            Toast.makeText(MainActivity.this, &quot;Configuration cannot be completed!&quot;, Toast.LENGTH_SHORT).show();
            
        }
    }, null);

}
</pre>
<p>در خط اول از متد SurfaceTexture یک شیء با نام surTexure ساختم. این متد داده‌های دریافتی از متد Surface را به فریم‌های قابل نمایش در TextureView تبدیل کرده و به آن ارسال می‌کند.<br />
در خط بعد اندازه پیش فرض تصویر دریافتی به واسطه متد setDefaultBufferSize تنظیم می‌شود. یعنی عرض (getWidth) و ارتفاع آن (getHeight).<br />
در ادامه از متد Surface یک شیء با نام mSurface ساختم که بافرهای خام دریافتی از دوربین را توسط OpenGL ES پردازش کرده و از طریق SurfaceTexture به ویجت TextureView ارسال می‌کند.<br />
قبلا در بدنه اکتیویتی یک CaptureRequest.Builder با نام capRequestBuilder تعریف کرده بودم. از این متد برای ساخت &#8220;درخواست گرفتن عکس&#8221; استفاده می‌شود. در اینجا درخواستی ارسال می‌گردد که مناسب قالب پیش نمایش دوربین یعنی TEMPLATE_PREVIEW باشد.<br />
در خط بعد توسط addTarget نمایه مدنظر یعنی mSurface را به لیست هدف‌های این درخواست (Request) اضافه می‌کنیم.<br />
سپس توسط متد createCaptureSession وضعیت &#8220;گرفتن عکس&#8221; را بررسی می‌کنیم. به دلیل پیکربندی دوربین و همچنین اختصاص بافر حافظه جهت ارسال تصویر به مقصد موردنظر، ایجاد یک session سنگین بوده و ممکن است زمان زیادی (حدود چندصد میلی ثانیه) طول بکشد.<br />
متد را به اینصورت در createCameraPreview تعریف می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/camera2api/createCaptureSession_method.png" alt="کار با createCaptureSession در اتصال اپلیکیشین به دوربین اندروید" /></p>
<p>بعد از اضافه شدن پارامتر دوم یعنی StateCallback، خطایی گرفته می‌شود که لازم است پارامتر سوم هم تعریف شود. مطابق کد زیر پارامتر سوم null تعریف شده:</p>
<pre class="brush: java; title: ; notranslate">
camDevice.createCaptureSession(Arrays.asList(mSurface), new CameraCaptureSession.StateCallback() {
    @Override
    public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {

    }

    @Override
    public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {

    }
}, null);
</pre>
<p class="pdfdl">جهت مطالعه ادامه آموزش، فایل PDF را دانلود نمائید</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<p class="copy">با توجه به اینکه آموزش‌های پایه با قیمت پایین در اختیار کاربر قرار گرفته و درآمد حاصل صرف تامین هزینه‌های وب سایت و تهیه آموزش‌های آتی می‌شود، به اشتراک گذاری این فایل با دیگران خلاف اخلاق است.</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه کامل این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۵۰<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : ۲۸ هزار تومان<br />
<i class="dlicon fa fa-square fa-lg"></i> تاریخ بروزرسانی آموزش : ۹۸/۹/۱۶<br />
<span class="notice">توجه: صرفا در صورتی از درگاه پشتیبان استفاده کنید که قادر به پرداخت از طریق سبد دانلود نباشید.</span><br />
<a href="https://android-studio.ir/checkout/?edd_action=add_to_cart&#038;download_id=166267" class="button green edd-submit" rel="noopener noreferrer"><i class="titleicon fa fa-shopping-cart fa-lg"></i>افزودن به سبد دانلود</a> <a href="https://atisdesign.ir/android-files/" class="button red edd-submit" target="_blank" rel="noopener noreferrer">درگاه پشتیبان</a>
</div>
<p>نوشته <a href="https://android-studio.ir/work-with-android-camera2-api/">کار با دوربین در اندروید توسط Camera۲ API</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/work-with-android-camera2-api/feed/</wfw:commentRss>
			<slash:comments>8</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

ذخیره سازی صفحه با استفاده از Disk: Enhanced 
بارگذاری تنبل (feed)

Served from: android-studio.ir @ 2026-07-13 00:21:05 by W3 Total Cache
-->