Для одного из текущих проектов понадобилось сделать контрол для множественного выбора элементов из списка.
Делать это отдельной Activity как-то не хотелось, но в Android нет готового компонента для этого. Максимум, что можно сделать, это использовать AlertDialog с множественным выбором. Обычный же Spinner позволяет выбрать только один элемент.
И вот, после гугления было найдено неплохое решение на StackOverflow, которое позволяет совместить внешний вид Spinner’а и функционал AlertDialog’а. Решение для Java можно посмотреть здесь. Это решение мне не очень понравилось из-за того, что работает только со строками + необходимо использовать дополнительный метод для установки элементов списка для выбора, вместо того, чтобы нормально испольщовать Adapter для этого.

После небольших допиливаний получился контрол, который, все-таки, можно использовать с адаптером. Правда, при этом теряется возможность хранить ссылку на объект адаптера где-то снаружи контрола и делать ему NotifyDataSetChanged() и NotifyDataSetInvalidated(), но для спиннера это очень редко используемый функционал, так что можно назвать решение приемлемым.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Util;

namespace SampleWidgets.Android.Views
{
    public class MultiSpinner : Spinner, 
		IDialogInterfaceOnMultiChoiceClickListener, 
		IDialogInterfaceOnCancelListener
    {
        public class MultiSpinnerSelectionEventArgs : EventArgs
        {
            public MultiSpinnerSelectionEventArgs(bool[] selected)
            {
                Selected = selected;
            }

            public bool[] Selected { get; private set; }
        }



        public delegate void ItemsSelectedHandler(object sender, 
			MultiSpinnerSelectionEventArgs args);
        public event ItemsSelectedHandler ItemsSelected;
        private ISpinnerAdapter RealAdapter;
        private bool[] selected;

        public MultiSpinner(IntPtr a, JniHandleOwnership b) : base(a, b) { }

        public MultiSpinner(Context context) : base(context)
        {
        }

        public MultiSpinner(Context context, IAttributeSet attrs) 
			: base(context, attrs)
        {
        }

        public MultiSpinner(Context context, IAttributeSet attrs, int defStyle) 
			: base(context, attrs, defStyle)
        {
        }

        public void OnClick(IDialogInterface dialog, int which, bool isChecked)
        {
            selected[which] = isChecked;
        }

        private ISpinnerAdapter CreateLabelAdapter()
        {
            List<string> names = new List<string>();
            int count = RealAdapter != null ? RealAdapter.Count : 0; 
            for (int i = 0; i < count; i++)
            {
                if (selected[i]) names.Add(RealAdapter.GetItem(i).ToString());
            }
            string label = string.Join(", ", names);
            if (label.Length == 0) 
				label = Context.GetString(Resource.String.LabelNone);
            return new ArrayAdapter<string>(Context,
                Android.Resource.Layout.sherlock_spinner_item,
                new string[] { label });
        }

        public void OnCancel(IDialogInterface dialog)
        {
            base.Adapter = CreateLabelAdapter();
            if (ItemsSelected != null) 
				ItemsSelected(this, new MultiSpinnerSelectionEventArgs(selected));
        }

        public override bool PerformClick()
        {
            AlertDialog.Builder builder = new AlertDialog.Builder(Context);
            List<string> names = new List<string>();
            int count = RealAdapter != null ? RealAdapter.Count : 0;
            if (count > 0)
            {
                for (int i = 0; i < count; i++)
                {
                    names.Add(RealAdapter.GetItem(i).ToString());
                }
                builder.SetMultiChoiceItems(names.ToArray(), selected, this);
                builder.SetPositiveButton(global::Android.Resource.String.Ok,
                    delegate(object o, DialogClickEventArgs e) 
					{
						(o as AlertDialog).Cancel(); 
					});
                builder.SetOnCancelListener(this);
                builder.Show();
            }
            return true;
        }

        public override ISpinnerAdapter Adapter 
        { 
            get { return RealAdapter; } 
            set 
            {
                selected = new bool[value.Count]; 
                RealAdapter = value;  
                base.Adapter = CreateLabelAdapter(); 
            } 
        }
    }
}

В коде можно заметить странного вида конструктор public MultiSpinner(IntPtr a, JniHandleOwnership b). Без него Mono for Android не хочет компилировать этот компонент т.к. в нем переопределены виртуальные методы и свойства. Решение проблемы с компиляцией было найдено на том же StackOverflow в этом топике. Ответ, который помечен как решение проблемы, хоть и описывает суть проблемы, но не предлагает реального решения. Зато добавление конструктора реально помогает.

Выглядит все это как-то так:
multi-spinner-screenshot

Previous Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

Leave a Reply

Your email address will not be published. Required fields are marked *

И.

Интегрируем оплату через PayPal в Android-приложение

С некоторых пор PayPal предлагает возможность добавления функционала, связанного с оплатой, непосредственно в Android-приложения. Пользователям, в этом случае, нет необходимости заходить на сайт PayPal, все происходит непосредственно в программе. Для этих целей доступен SDK. О том, как его использовать я расскажу в этот раз.

Настройка приложения

Для начала качаем SDK с официального сайта.

Затем создаем Android-приложение в Eclipse.

В архиве с PayPal SDK есть jar-файл PayPal_MPL.jar, который нам необходимо положить в подкаталог lib внутри каталога с исходным кодом нашего приложения (подкаталог lib создаем руками, никто за нас автоматически этого не сделает).

Затем идем в настройки проекта в раздел Java Build Path и на вкладке Libraries с помощью кнопки Add JARs… добавляем эту библиотеку в список используемых в нашем проекте.

В созданном приложении в манифесте необходимо добавить следующие строки:

<activity android:name="com.paypal.android.MEP.PayPalActivity"
                    android:theme="@android:style/Theme.Translucent.NoTitleBar"
                    android:configChanges="keyboardHidden|orientation"/>

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

После этого можно приступать к написанию кода.

Кодинг

SDK позволяет создавать брендированные кнопки для вызова окна оплаты. И вот при создании activity (в методе onCreate()) мы создадим такую кнопку и добавим в layout.

private static final String PAYPAL_APP_ID = "APP-80W284485P519543T";
PayPal mPayPal;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    mPayPal = PayPal.initWithAppID(
				MainActivity.this.getBaseContext(),
				PAYPAL_APP_ID,
				PayPal.ENV_SANDBOX);
    CheckoutButton payButton = mPayPal.getPaymentButton(PayPal.BUTTON_278x43, this, PayPal.PAYMENT_TYPE_HARD_GOODS);
   payButton.setOnClickListener(mPayButtonListener);
   LinearLayout mainLayout = (LinearLayout)findViewById(R.id.main_layout);
   mainLayout.addView(payButton,
       		new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
}

Как видно из кода, у нас используется строка с PayPal Application ID. В примере используется тестовый ID приложения. Его можно посмотреть на странице аккаунта разработчика после регистрации на X.com.

Также хотелось бы отметить, что PayPal позволяет тестировать приложения с использованием “песочницы” (sandbox), а уже после тестирования можно настроить все так, чтобы приложение работало с реальным сайтом. В тестовом приложении мы указываем тип подключения PayPal.ENV_SANDBOX, который указывает что мы используем “песочницу”.

После того, как мы выполнили указанные выше действия, у нас должно получиться что-то вроде этого:

Отлично, кнопку мы добавили, но это всего лишь кнопка, она ничего не делает. Для того, чтобы она что-то делала, необходимо к ней добавить обработчик нажатия:

OnClickListener mPayButtonListener = new OnClickListener() {

		@Override
		public void onClick(View v) {
			PayPalPayment newPayment = new PayPalPayment();

			newPayment.setAmount("5.00");
			newPayment.setCurrency("USD");
			newPayment.setRecipient("zombie_1285772993_biz@wxwidgets.info");
			newPayment.setItemDescription("My Super Item");
			newPayment.setMerchantName("John Doe");

			Intent paypalIntent = new Intent(MainActivity.this, PayPalActivity.class);
			paypalIntent.putExtra(PayPalActivity.EXTRA_PAYMENT_INFO, newPayment);
			MainActivity.this.startActivityForResult(paypalIntent, 1);
		}
	};

E-mail получателя платежа можно получить в песочнице, предварительно создав аккаунт продавца в разделе Test Accounts.

После нажатия на кнопку на экране появляется activity, в которой можно произвести оплату:

E-mail отправителя платежа можно также получитьв “песочнице” после создания тестового аккаунта.

После завершения оплаты нам необходимо как-то узнать результат (оплата может быть отменена пользователем, может произойти ошибка отправки средств и т.д.). Для этого нам необходимо реализовать метод onActivityResult():

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    	String label = "";
    	String transactionID = data.getStringExtra(PayPalActivity.EXTRA_TRANSACTION_ID);
    	switch(resultCode) {
    		case Activity.RESULT_OK:
    			label = "OK";
    			break;
    		case Activity.RESULT_CANCELED:
    			label = "Cancelled";
    			break;
    		case PayPalActivity.RESULT_FAILURE:
    			label = "Failure";
    			break;
      	}
    	TextView resultLabelView = (TextView)findViewById(R.id.result_label);
    	resultLabelView.setText(label + ": " + transactionID);
}

Вместе с SDK поставляется неплохая документация, так что все вопросы решаются довольно быстро. В принципе, работа с PayPal MPL оставила только положительные ощущения, все просто и понятно, не глючит. Примеры, которые идут в поставке тоже работают нормально. Видно что SDK – цельный продукт.

Ну вот, собственно и все.

Напоследдок несколько ссылок:

Исходные коды тестового приложения.

П.

Презентация первого Android-устройства, HTC Dream, состоится 23го сентября

Презентация HTC Dream, превого Linux-смартфона на платформе Google Android анонсирована на 23е сентября. Мероприятие, проводимое оператором сотовой связи T-Mobile, состоится в Нью-Йорке.

По данным Wall Street Journal, T-Mobile начнет продажи HTC Dream во второй половине октября этого года. HTC планирует продать в этом году до 700 тысяч устройств HTC Dream.
Для тех разработчиков мобильных приложений, которые уже начали разработку для Goole Android, появление первго устройства для этой платформы может стать неплохим подспорьем в процессе отладки и тестирования своих приложений.

Для тех же, кто еще только думает начать разрабатывать ПО для Android-устройств, думаю, будут интересны следующие ссылки: