Ответы пользователя по тегу Android
  • Как определить статус приложения, что оно свёрнуто?

    enq3
    @enq3
    Android engineer at #ITX5
    Я рассуждаю так. Раз приложение свернули, значит активность больше нам не видна. И должен сработать метод onStop. Но из активности можно выйти нажатием кнопки назад, и это тоже вызовет onStop , надо это обработать. Также onStop сработает, если мы запустим новую активность в нашем приложении или повернем устройство.

    BaseActivity.java
    public class BaseActivity extends AppCompatActivity {
        protected boolean isShowAlert = true;
        private int oldOrientation;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            oldOrientation = getResources().getConfiguration().orientation;
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            int currentOrientation = getResources().getConfiguration().orientation;
            if(currentOrientation != oldOrientation)
                isShowAlert = false;
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            isShowAlert = true;
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            if (isShowAlert)
                AppHideHandler();
        }
    
        @Override
        public void onBackPressed() {
            isShowAlert = false;
            super.onBackPressed();
        }
    
        public void AppHideHandler() {
            AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext());
            builder.setTitle("Сообщение")
                    .setMessage("Приложение свернули")
                    .setCancelable(true);
            AlertDialog alert = builder.create();
            alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
            alert.show();
        }
    }


    За показ сообщения отвечает isShowAlert. Не забываем ставить ему true в onStart, т.к. при запуске новой активности это ключ будет установлен в false и при возврате обратно уже не позволит отслеживать сворачивание.
    В onPause сравниваем конфигурацию, если устройство повернули, значит показывать сообщение не нужно.
    В AndroidManifest.xml надо добавить:
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>


    Наследуемся от нашей базовой активности. И теперь по умолчанию при сворачивании будет показываться сообщение.
    MainActivity.java
    public class MainActivity extends BaseActivity {
    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btn = (Button) findViewById(R.id.button);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    isShowAlert = false;
                    startActivity(new Intent(MainActivity.this, Main2Activity.class));
                }
            });
        }
    }

    Также при запуске новой активности ставим ключ в false, чтобы сообщение не показывалось.

    В данной реализации сообщение будет показано, если вызвать список запущенных ранее приложений (долгий тап по кнопке Home).
    Ответ написан
    Комментировать
  • Как задать стиль для toolbar, который используется вместо ActionBar?

    enq3
    @enq3
    Android engineer at #ITX5
    Можно задать любую разметку, Toolbar это контейнер:
    <android.support.v7.widget.Toolbar
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/toolbar"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:minHeight="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            android:popupTheme="@android:style/ThemeOverlay.Material.Light"
            app:contentInsetLeft="0dp"
            app:contentInsetStart="0dp" >
       <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:background="#ffffff">
      </LinearLayout>
    </android.support.v7.widget.Toolbar>
    Ответ написан
    Комментировать
  • Как вызвать Toast с другого класса?

    enq3
    @enq3
    Android engineer at #ITX5
    Это сейчас надо показывать только toast, а потом понадобится состояние контролов в активности менять или вернуть результат работы класса, и одной передачей контекста уже не обойдешься.

    MyClass.java
    public class MyClass {
        MyClassCallback callback;
    
        public MyClass() {
            if(callback != null)
                callback.done();
        }
    
        public void setCallback(MyClassCallback callback) {
            this.callback = callback;
        }
    
        public void perform() {
            if(callback != null)
                callback.done();
        }
    
        interface MyClassCallback {
            void done();
        }
    }


    MainActivity.java
    public class MainActivity extends AppCompatActivity {
        private MyClass myClass;
        private MyClass.MyClassCallback myClassCallback;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            myClass = new MyClass();
            myClassCallback = new MyClass.MyClassCallback() {
                @Override
                public void done() {
                    Toast.makeText(MainActivity.this, "Мое сообщение", Toast.LENGTH_LONG).show();
                }
            };
            myClass.setCallback(myClassCallback);
            myClass.perform();
        }
    }
    Ответ написан
    Комментировать
  • Как выделить элемент ListView?

    enq3
    @enq3
    Android engineer at #ITX5
    Ответ написан
    Комментировать
  • Как мне это реализовать?

    enq3
    @enq3
    Android engineer at #ITX5
    А если вот так?
    activity_main.xml
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:listSelector="@color/item_color_test"
            android:id="@+id/listView"/>
    
    </RelativeLayout>


    MainActivity.java
    public class MainActivity extends AppCompatActivity {
        private final static String KEY = "selectedRow";
    
        private MyAdapter adapter;
        private int selectedItem = 1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ListView listView = (ListView) findViewById(R.id.listView);
            final String[] data = {
                    "a","v","c"
            };
            if(savedInstanceState != null && savedInstanceState.containsKey(KEY))
            {
                selectedItem = savedInstanceState.getInt(KEY);
            }
            adapter = new MyAdapter(this, R.layout.item, data, selectedItem);
            listView.setAdapter(adapter);
            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    String selected = data[position];
                    selectedItem = position;
                    adapter.setSelectedRow(selectedItem);
                }
            });
        }
    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putInt(KEY, selectedItem);
        }    
    
        private class MyAdapter extends ArrayAdapter<String> {
            final int resId;
            int selectedRow;
    
            public MyAdapter(Context context, int resId, String[] data, int selectedRow) {
                super(context, resId, data);
                this.resId = resId;
                this.selectedRow = selectedRow;
                selectedColor = getResources().getColor(R.color.item_color_test);
            }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                String string = getItem(position);
    
                if (convertView == null) {
                    convertView = LayoutInflater.from(getContext()).inflate(resId, null);
                }
                TextView textView = (TextView) convertView;
                textView.setText(string);
                textView.setBackgroundColor(position == selectedRow ? selectedColor : 0x00000000);
                return convertView;
            }
    
            public void setSelectedRow(int row)
            {
                selectedRow = row;
                notifyDataSetInvalidated();
            }
        }
    }


    item.xml
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:paddingTop="20dp"
        android:paddingBottom="20dp"
        android:paddingLeft="10dp"/>


    В собственной реализации адаптера с шаблоном строки списка можно делать что угодно.
    Ответ написан
    Комментировать
  • Ripple эффект с произвольными формами?

    enq3
    @enq3
    Android engineer at #ITX5
    Я делал контролы с полной отрисовкой всей графики на Canvas. И есть ограничение, придется пожертвовать аппаратным ускорением, но я визуально разницу не особо заметил.
    Полный код класса:
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    import com.nineoldandroids.animation.Animator;
    import com.nineoldandroids.animation.ValueAnimator;
    
    public class RippleCircle extends View {
        // ===========================================================
        // Constants
        // ===========================================================
        private static final String TAG = RippleCircle.class.getSimpleName();
        private static final int RIPPLE_COLOR = 0x66000000;
        private static final int RIPPLE_FADE_COLOR = 0x22000000;
        private static final int FAB_COLOR = 0xffcccccc;
        private static final long ANIM_DURATION = 250;
        protected static int FLAGS = Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
        // ===========================================================
        // Fields
        // ===========================================================
        private Paint mButtonBg;
        private Paint mRippleButtonBg;
        private Path main;
        private ValueAnimator animatorRipple;
        private float selRadius;
        private float radius;
        private float centerY;
        private float centerX;
        private float rippleX;
        private float rippleY;
        private boolean isButtonTouchDown;
        private boolean isProgress;     
        // ===========================================================
        // Constructors
        // ===========================================================
        public RippleCircle(Context context) {
            super(context);
            init();
        }
    
        public RippleCircle(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        // ===========================================================
        // Getter & Setter
        // ===========================================================
    
        // ===========================================================
        // Methods for/from SuperClass/Interfaces
        // ===========================================================
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawCircle(centerX, centerY, radius, mButtonBg);
            canvas.clipPath(main);
            canvas.drawCircle(rippleX, rippleY, selRadius, mRippleButtonBg);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            float size = Math.min(w, h);
            centerY = size / 2;
            centerX = size / 2;
            radius = size / 2;
            main.addCircle(centerX, centerY, radius, Path.Direction.CCW);
            animatorRipple.setFloatValues(0, radius);
        }
        // ===========================================================
        // Inner Methods
        // ===========================================================
        private void init() {
            main = new Path();
    
            animatorRipple = ValueAnimator.ofFloat(0, 0);
            animatorRipple.setDuration(ANIM_DURATION);
            animatorRipple.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    selRadius = (Float) animation.getAnimatedValue();
                    if (selRadius / radius <= 1 && selRadius / radius >= 0)
                        mRippleButtonBg.setColor(blendColors(RIPPLE_COLOR, RIPPLE_FADE_COLOR, 1 - selRadius / radius));
                    invalidate();
                }
            });
    
            animatorRipple.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (!isButtonTouchDown) {
                        selRadius = 0;
                        invalidate();
                        isProgress = false;
                    }
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
    
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
    
                }
            });
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
                setLayerType(LAYER_TYPE_SOFTWARE, null);
            }
            mButtonBg = new Paint(FLAGS);
            mButtonBg.setColor(FAB_COLOR);
    
            mRippleButtonBg = new Paint(FLAGS);
            mRippleButtonBg.setColor(RIPPLE_COLOR);
    
            setOnTouchListener(new OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
    
                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            rippleX = event.getX();
                            rippleY = event.getY();
                            if (isPointInCircle(centerX, centerY, event.getX(), event.getY(), radius)) {
                                isButtonTouchDown = true;
                                if (!isProgress) {
                                    rippleX = event.getX();
                                    rippleY = event.getY();
                                    isProgress = true;
                                    float max = distance(centerX, centerY, rippleX, rippleY) + radius;
                                    animatorRipple.setFloatValues(0, max);
                                    animatorRipple.start();
                                }
                            }
                            break;
                        case MotionEvent.ACTION_UP:
                            isButtonTouchDown = false;
                            if (!animatorRipple.isRunning()) {
                                selRadius = 0;
                                invalidate();
                                isProgress = false;
                            }
                            break;
                    }
                    return true;
                }
            });
        }
    
        private boolean isPointInCircle(float centerX, float centerY, float x, float y, float radius) {
            return distance(centerX, centerY, x, y) <= radius;
        }
    
        private float distance(final float pX1, final float pY1, final float pX2, final float pY2) {
            final float dX = pX2 - pX1;
            final float dY = pY2 - pY1;
            return (float) Math.sqrt((dX * dX) + (dY * dY));
        }
    
        private int blendColors(int color1, int color2, float ratio) {
            final float inverseRation = 1f - ratio;
            float a = (Color.alpha(color1) * ratio) + (Color.alpha(color2) * inverseRation);
            float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
            float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
            float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
            return Color.argb((int) a, (int) r, (int) g, (int) b);
        }
        // ===========================================================
        // Inner and Anonymous Classes
        // ===========================================================
    }

    Используем в разметке:
    <yourPackage.RippleCircle
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_centerInParent="true"/>

    По аналогии можно добавить какую угодно форму, я делал прямоугольник со скругленными углами и окружность.
    Ответ написан
    4 комментария
  • Можно ли создать два AsyncTask'a в одном классе?

    enq3
    @enq3
    Android engineer at #ITX5
    Можно. Начиная с версии 1.6 по умолчанию таски выполнялись в пуле потоков, что позволяло запускать несколько тасков параллельно. А вот начиная с версии 3.0 это поведение по умолчанию убрали, чтобы исключить ошибки с параллельным выполнением кода. Если действительно требуется выполнять несколько тасков одновременно, то используем метод executeOnExecutor. Но держим в уме, что при повороте устройства AsyncTask будет пересоздан.
    if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ) {
        new MyTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    } else {
        new MyTask().execute();
    }

    Не рекомендую использовать IntentService для данной задачи, т.к. IntentService помещает все запросы в очередь и выполняет их по-одному, пока очередь не опустеет.

    Посмотрите в сторону Bound Service.
    Ответ написан
    Комментировать