понедельник, 17 сентября 2012 г.

Работа с ActionBarSherlock

Медиа-плеер и буфферинг видео по HTTP

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

Ещё, поведение VideoView/SurfaceView при переключении видов в пределе одного лэйаута тоже работает очень неоднозначно (чёрный экран через раз), поэтому пришлось банально оставлять в лэйауте один-единственный VideoView и показывать ProgressDialog поверх него, пока видео загружается. Опять же, если вы знаете что-то про потоковое воспроизведение видео средствами MediaPlayer (или о получении чанков вручную), пишите в комментариях.

Поэтому, если в вашем случае вам хватит вызова MediaPlayer.setDataSource(Uri uri), можете пропустить следующий абзац, большего в ней не рассказывается.

Если же вам тоже пришлось получать поток вручную, я обращу ваше внимание на пару моментов, в остальном просто продемонстрирую код, он должен рассказать всё сам:
Пример из vimeoid: VimeoVideoPlayingTask
Вызывается из активити: Player
Лэйаут: player.xml
Загружать поток лучше используя AsyncTask. Я просто агрегирую MediaPlayer внутри ...PlayingTask для удобства, вы можете выбрать любой другой способ, но получать поток определённо лучше через AsyncTask. При этом, в методе onPreExecute можно подготовить плеер и настроить его, в doInBackground получить поток видео и вернуть этот поток в onPostExecute, в котором и запустить проигрывание. Опять же, удобно показывать процентный прогресс загрузки, потому что в doInBackground известно количество полученных данных.

Если при загрузке потока возникает исключение, сообщение о нём приходится показывать через runOnUiThread, потому что выполнение задачи было прервано.

Выполнение getWindow().setFormat(PixelFormat.TRANSPARENT); предназначено, чтобы отображённые поверх плеера виды не оставались поверх него после скрытия. Хотя если нужно использовать ViewSwitcher, это всё равно не помогает.

Код получения потока по URL примерно таков:

public static InputStream getVideoStream(long videoId)
throws FailedToGetVideoStreamException, VideoLinkRequestException {
try {
final HttpClient client = new DefaultHttpClient();
. . .
HttpResponse response = client.execute(request);
if ((response == null) || (response.getEntity() == null))
throw new FailedToGetVideoStreamException("Failed to get video stream");
lastContentLength = response.getEntity().getContentLength();
return response.getEntity().getContent();
} catch (URISyntaxException use) {
throw new VideoLinkRequestException("URI creation failed : " + use.getLocalizedMessage());
} catch (ClientProtocolException cpe) {
throw new VideoLinkRequestException("Client call failed : " + cpe.getLocalizedMessage());
} catch (IOException ioe) {
throw new VideoLinkRequestException("Connection failed : " + ioe.getLocalizedMessage());
}
}

Принудительная инвалидация видов в списках

ListView в Android, как известно, устроены с небольшой хитростью, эта хитрость — ListView Recycler. Приницип Recycler'а, если кратко, состоит в том, что если в списке элементов больше, чем вмещается на экран, при прокручивании списка виды новых элементы не создаются, а переиспользуются виды старых — на этом приниципе работают имплементации getView в адаптерах.

Если в какой-то момент требуется обновить (инвалидировать) конкретный известный вид элемента (или даже его дочерний вид) списка в то время, когда он видим на экране, можно вызвать ListView.invalidate() или Adapter.notifyDataSetChanged(), но иногда эти методы нерационально обновляют и соседние виды, а то и вообще все видимые (особенно если layout построен неправильно). Есть способ получить текущий вид элемента списка используя метод ListView.getChildAt(position). Однако position в данном случае это не индекс элемента в списке, как можно было бы ожидать, а индекс относительно видимых на экране видов. Поэтому полезными будут такие методы:

public static View getItemViewIfVisible(AdapterView<?> holder, int itemPos) {
int firstPosition = holder.getFirstVisiblePosition();
int wantedChild = itemPos - firstPosition;
if (wantedChild < 0 || wantedChild >= holder.getChildCount()) return null;
return holder.getChildAt(wantedChild);
}

public static void invalidateByPos(AdapterView<?> parent, int position) {
final View itemView = getItemViewIfVisible(parent, position);
if (itemView != null) itemView.invalidate();
}

invalidateByPos обновляет вид только если он видим на экране (насильно вызывая getView адаптера), а если элемент не видим — getView адаптера будет вызван автоматически когда этот вид появится в области видимости при прокрутке списка. Чтобы обновить некий дочерний вид элемента, вы можете использовать метод getViewIsVisible, он вернёт вид элемента из которого можно получить доступ к его дочерним видам и null, если вид не видим пользователю и в обновлении нет необходимости.
Методы описаны в классе: Utils

Видео урок по созданию иконки для приложения под iOS

Конечный результат поражает. http://vimeo.com/36302707

Navigation tabs с Fragments использую ActionBarSherlock

This post covers how to use navigation tabs in the ActionBar using ActionBarSherlock. Although the demo in the ActionBarSherlock download shows how to create navigation tabs, they are not associated with Fragments. I looked around online and could not find any tutorials for it. Now that I’ve figured it out, I thought I’d share it.

This tutorial has two Fragments, FragmentA and FragmentB, each of which has a TextView and a Button in their layouts.

Here’s FragmentA.java: It implements an OnClickListener for the Button to show a Toast.

public class FragmentA extends Fragment {
Button button;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle saved)
{
return inflater.inflate(R.layout.frag_a, group, false);
}
@Override
public void onActivityCreated (Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
button = (Button) getActivity().findViewById(R.id.button1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(), "You clicked button on Fragment A", Toast.LENGTH_LONG).show();
}
});
}
}

Here’s FragmentB.java: It also implements the Button OnClickListener, but to display a dialog.


public class FragmentB extends Fragment {
Button button;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle saved)
{
return inflater.inflate(R.layout.frag_b, group, false);
}
@Override
public void onActivityCreated (Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
button = (Button) getActivity().findViewById(R.id.button2);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Fragment B");
builder.setMessage("What would you like to do?");
builder.setPositiveButton("Nothing", null);
builder.setNegativeButton("Leave me alone!", null);
builder.show();
}
});
}
}

And finally, here’s the Activity that hosts these Fragments, FragmentDemoActivity


public class FragmentDemoActivity extends SherlockFragmentActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar bar = getSupportActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ActionBar.Tab tab1 = bar.newTab();
ActionBar.Tab tab2 = bar.newTab();
tab1.setText("Fragment A");
tab2.setText("Fragment B");
tab1.setTabListener(new MyTabListener());
tab2.setTabListener(new MyTabListener());
bar.addTab(tab1);
bar.addTab(tab2);
}
private class MyTabListener implements ActionBar.TabListener
{
@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if(tab.getPosition()==0)
{
FragmentA frag = new FragmentA();
ft.replace(android.R.id.content, frag);
}
else
{
FragmentB frag = new FragmentB();
ft.replace(android.R.id.content, frag);
}
}
@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
}
}

Таблицы цветов

Часто приходится менять цвета различных элементов при создании приложения. Хорошая таблица цветов представлена на сайте http://ru.wikipedia.org/wiki/Википедия:Таблица_цветов

Создание анимации для Button

Create four XML files for animation:
/res/anim/anim_alpha.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.1"
android:duration="500"
android:repeatCount="1"
android:repeatMode="reverse" />
</set>


/res/anim/anim_rotate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<rotate
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="500"
android:startOffset="0"
android:repeatCount="1"
android:repeatMode="reverse" />
</set>


/res/anim/anim_scale.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<scale
android:fromXScale="1.0"
android:toXScale="3.0"
android:fromYScale="1.0"
android:toYScale="3.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="500"
android:repeatCount="1"
android:repeatMode="reverse" />
</set>


/res/anim/anim_translate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<translate
android:fromXDelta="0"
android:toXDelta="100%p"
android:duration="500"
android:repeatCount="1"
android:repeatMode="reverse"/>
</set>


main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />

<Button
android:id="@+id/translate"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Translate" />
<Button
android:id="@+id/alpha"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Alpha" />
<Button
android:id="@+id/scale"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Scale" />
<Button
android:id="@+id/rotate"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Rotate" />

</LinearLayout>


Main activity:
package com.exercise.AndroidAnimButtons;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;

public class AndroidAnimButtonsActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

final Animation animTranslate = AnimationUtils.loadAnimation(this, R.anim.anim_translate);
final Animation animAlpha = AnimationUtils.loadAnimation(this, R.anim.anim_alpha);
final Animation animScale = AnimationUtils.loadAnimation(this, R.anim.anim_scale);
final Animation animRotate = AnimationUtils.loadAnimation(this, R.anim.anim_rotate);

Button btnTranslate = (Button)findViewById(R.id.translate);
Button btnAlpha = (Button)findViewById(R.id.alpha);
Button btnScale = (Button)findViewById(R.id.scale);
Button btnRotate = (Button)findViewById(R.id.rotate);

btnTranslate.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View arg0) {
arg0.startAnimation(animTranslate);
}});

btnAlpha.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View arg0) {
arg0.startAnimation(animAlpha);
}});

btnScale.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View arg0) {
arg0.startAnimation(animScale);
}});

btnRotate.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View arg0) {
arg0.startAnimation(animRotate);
}});
}
}

ВКонтакте Android SDK

SDK опубликовано на гитхабе https://github.com/thest1/Android-VKontakte-SDK. Распространяется по лицензии MIT. Вы можете её использовать в любых проектах, в том числе коммерческих. Можете её модифицировать. Ссылку на первоисточник желательно размещать по возможности.

Быстро понять принципы работы библиотеки вам поможет демо-проект AndroidVkSdkSample. В нём реализована авторизация и, в качестве примера, отправка записи на свою стену.

Чтобы подключить библиотеку к своему проекту, нужно выполнить следующие шаги:

Импортировать проект AndroidVkSdk в Eclipse. AndroidVkSdk реализован как Android Library Project.
Добавить в своём проекте ссылку на AndroidVkSdk.
Убедиться что у вашего приложения есть разрешение на доступ в Интернет

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

Создать url авторизации и открыть его в WebView

String url=Auth.getUrl(API_ID, Auth.getSettings());
webview.loadUrl(url);

 Здесь в качестве API_ID должен быть указан ID вашего приложения, созданного на http://vk.com/developers.php
Дождаться когда webview будет перенаправлен на Auth.redirect_url. Распарсить redirect_url чтобы получить из него access_token. Для примера смотрите как это сделано в LoginActivity.java в проекте AndroidVkSdkSample.

String[] auth=Auth.parseRedirectUrl(url);

Создать объект Api для выполнения запросов к серверу ВКонтакте:

API api=new Api(access_token, API_ID);

Теперь можно выполнять запросы к серверу, например так:

api.createWallPost(user_id, text, null, null, false, false, false, null, null);

Разработка иконок под Android

Нашел отличный сайт для генерации различных иконок, которые потребуются при создании приложения под android.
http://android-ui-utils.googlecode.com/hg/asset-studio/dist/index.html 

Создание SearchView с использованием SherlockActionBar

В этом посте рассказывается как создать SearchView с использованием библиотеки SherlockActionBar.
Класс MainActivity должен быть наследником класса  SherlockActivity и интерфейса  OnQueryTextListener.
public class MainActivity extends SherlockActivity implements SearchView.OnQueryTextListener{

Далее создаем переменную типа  SearchView.
SearchView searchView;

В методе  onCreateOptionsMenu создаем searchItem, который будет частью нашего экшен бара. В методе add константа SEARCH является уникальным индитификатором.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItem searchItem = menu.add(Menu.NONE, SEARCH, Menu.NONE, R.string.search);
searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
| MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
searchView = new SearchView(getApplicationContext());
searchItem.setActionView(searchView);
 searchView.setOnQueryTextListener(this);
return true;
}

Следующие два метода необходимы, т.к. мы унаследовали интерфейс  OnQueryTextListener.

public boolean onQueryTextChange(String newText) {
return false;
}

public boolean onQueryTextSubmit(String query) {
return true;
}