Android Un widget dinámico en forma de tarjetas basado en StackView (algo así como Tinder)

UPD: el widget ya no tiene errores, todo está arreglado

Hola a todos

Hace un mes, se publicó un artículo sobre mi primera solicitud de contabilidad para las finanzas personales y familiares.

Desde entonces, cambié el nombre a "Presupuesto y carrito de compras" y, en consecuencia, agregué funcionalidades que le permiten crear rápidamente un carrito de compras y usarlo.

Widget de carrito



El mecanismo para crear un carrito de compras es bastante simple, lógico y, por lo tanto, conveniente:
En el árbol de directorios

Usted marca varios artículos dentro de la categoría y hace clic en el botón "Agregar al carrito". Esta lista de posiciones con la categoría raíz se muestra en forma de una tarjeta de widget. Esta acción puede repetirse tantas veces como sea necesario.

Como resultado, hemos agrupado lógicamente las listas de compras.
La belleza es que aparece inmediatamente en todos los miembros del grupo, es decir. Mamá creó tarjetas, y papá condujo por el camino y compró todo.
Haga clic en la tarjeta y se abrirá el formulario para ingresar el monto. Como resultado, ingrese la cantidad no para cada artículo, sino para la categoría principal en su conjunto. Es decir la idea es que usemos las categorías detalladas para el plan de compra, y la cantidad se tiene en cuenta de manera más gruesa por la categoría principal.


Implementación

1. Clase de servicio
public class StackWidgetService extends RemoteViewsService { @Override public void onCreate() { super.onCreate(); } @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new StackRemoteViewsFactory(this.getApplicationContext(), intent); } } 


2. Clase de adaptador para la pila de cartas
 public class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private List<MoneyTransaction> mWidgetItems = new ArrayList<MoneyTransaction>(); private Context mContext; @Inject FirestoreRepository repository; public StackRemoteViewsFactory(Context context, Intent intent) { mContext = context; Injector.getApplicationComponent().inject(this); } public void onCreate() { } public void onDestroy() { // repository.getLiveOrders().removeObserver(observer); // In onDestroy() you should tear down anything that was setup for your data source, // eg. cursors, connections, etc. mWidgetItems.clear(); } public int getCount() { return mWidgetItems.size(); } public RemoteViews getViewAt(int position) { // position will always range from 0 to getCount() - 1. // We construct a remote views item based on our widget item xml file, and set the // text based on the position. RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_stack_orders_item); if(position < mWidgetItems.size()) { MoneyTransaction moneyTransaction = mWidgetItems.get(position); String dateStr = FormatTool.getDateStringShort(moneyTransaction.getDate()); rv.setTextViewText(R.id.tv_date, dateStr); rv.setTextViewText(R.id.tv_correspondent, moneyTransaction.getCategoryObject().getName()); rv.setTextViewText(R.id.tv_comment, moneyTransaction.getComment()); Date today = FormatTool.getStartOfPeriod(Calendar.getInstance().getTime(), Constants.PERIODICITY_DAY); Date date = FormatTool.getStartOfPeriod(moneyTransaction.getDate(), Constants.PERIODICITY_DAY); long days = TimeUnit.MILLISECONDS.toDays(today.getTime() - date.getTime()); if(days < 0) { rv.setInt(R.id.widget_item, "setBackgroundResource", R.drawable.blue_with_border); } else if(days == 0) { rv.setInt(R.id.widget_item, "setBackgroundResource", R.drawable.green_with_border); } else if(days == 1) { rv.setInt(R.id.widget_item, "setBackgroundResource", R.drawable.orange_with_border); } else { rv.setInt(R.id.widget_item, "setBackgroundResource", R.drawable.red_with_border); } // Next, we set a fill-intent which will be used to fill-in the pending intent template // which is set on the collection view in StackWidgetProvider. Bundle extras = new Bundle(); extras.putString(FirestoreTables.PATH, moneyTransaction.getPath()); Intent fillInIntent = new Intent(); fillInIntent.setAction(OrdersWidgetProvider.CLICK_ACTION); fillInIntent.putExtras(extras); rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent); } // Return the remote views object. return rv; } public RemoteViews getLoadingView() { // You can create a custom loading view (for instance when getViewAt() is slow.) If you // return null here, you will get the default loading view. return null; } public int getViewTypeCount() { return 1; } public long getItemId(int position) { return position; } public boolean hasStableIds() { return true; } public void onDataSetChanged() { mWidgetItems = repository.getOrdersSync(); } } 


3. Clase de proveedor de widgets
        .       -  2 : -             ( onDataSetChanged     ) -        -        . public class OrdersWidgetProvider extends AppWidgetProvider { public static final String CLICK_ACTION = "click_action"; private Observer observer; @Inject FirestoreRepository repository; public OrdersWidgetProvider() { super(); Injector.getApplicationComponent().inject(this); } @Override public void onReceive(Context context, Intent intent) { if(CLICK_ACTION.equals(intent.getAction())) { String path = intent.getStringExtra(FirestoreTables.PATH); repository.getTransaction(path) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(transaction -> { if(transaction != null) { Intent launchIntent = new Intent(context, TransactionDetailActivity.class); launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); launchIntent.putExtra(Constants.TRANSACTION, transaction); context.startActivity(launchIntent); } }); } super.onReceive(context, intent); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { LiveData<List<MoneyTransaction>> liveData = repository.getLiveOrders(); if(observer != null && liveData.hasActiveObservers()) { liveData.removeObserver(observer); } observer = (Observer<List<MoneyTransaction>>) moneyTransactions -> { repository.setOrders(moneyTransactions); appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.stack_view); }; liveData.observeForever(observer); // update each of the widgets with the remote adapter for (int i = 0; i < appWidgetIds.length; ++i) { // Here we setup the intent which points to the StackViewService which will // // provide the views for this collection. Intent intent = new Intent(context, StackWidgetService.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); // // When intents are compared, the extras are ignored, so we need to embed the extras // // into the data so that the extras will not be ignored. intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_stack_orders_layout); rv.setRemoteAdapter(R.id.stack_view, intent); // // // The empty view is displayed when the collection has no items. It should be a sibling // // of the collection view. rv.setEmptyView(R.id.stack_view, R.id.empty_view); // // Here we setup the a pending intent template. Individuals items of a collection // // cannot setup their own pending intents, instead, the collection as a whole can // // setup a pending intent template, and the individual items can set a fillInIntent // // to create unique before on an item to item basis. Intent intent1 = new Intent(context, OrdersWidgetProvider.class); intent1.setAction(CLICK_ACTION); intent1.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent1, PendingIntent.FLAG_UPDATE_CURRENT); rv.setPendingIntentTemplate(R.id.stack_view, pendingIntent); appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds[i], R.id.stack_view); appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } super.onUpdate(context, appWidgetManager, appWidgetIds); } @Override public void onDisabled(Context context) { super.onDisabled(context); repository.getLiveOrders().removeObserver(observer); } } 


4. Diseño del widget
 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <StackView android:id="@+id/stack_view" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:loopViews="true"/> <LinearLayout android:id="@+id/empty_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/round_light_button_switcher" android:orientation="vertical"> <ImageView android:layout_width="wrap_content" android:layout_height="200dp" android:layout_margin="2dp" android:src="@drawable/beans_horizontal"/> <TextView style="@style/PrimaryDarkBold26" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="20dp" android:layout_marginTop="-40dp" android:layout_marginRight="20dp" android:layout_marginBottom="20dp" android:gravity="bottom|center_horizontal" android:text="@string/empty_view_text" android:textStyle="bold"/> </LinearLayout> </FrameLayout> 


5. Marcado de la tarjeta
 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/widget_item" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv_correspondent" style="@style/White20" android:layout_width="match_parent" android:layout_height="50dp" android:ellipsize="end" /> <LinearLayout style="@style/WhiteDividerStyle" android:layout_marginTop="4dp"/> <TextView android:id="@+id/tv_comment" style="@style/White18" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="4dp" android:ellipsize="end" android:maxLines="20"/> <LinearLayout style="@style/WhiteDividerStyle"/> <TextView android:id="@+id/tv_date" style="@style/White18" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="4dp"/> </LinearLayout> 


6. Descripción del proveedor XML
 <?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget_stack_orders_layout" android:minWidth="150dp" android:minHeight="200dp" android:previewImage="@drawable/widget"> </appwidget-provider> 

Source: https://habr.com/ru/post/452998/


All Articles