ver 2.0
This commit is contained in:
1 parent
e9dcb2b394
commit
e39e393b8b
11 files changed
+503
No files matched your search
@@ -34,6 +34,13 @@ dependencies {
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
||||
implementation 'com.google.zxing:core:3.5.3'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation 'org.tensorflow:tensorflow-lite:2.14.0'
|
||||
implementation 'org.tensorflow:tensorflow-lite-gpu:2.14.0'
|
||||
implementation 'org.tensorflow:tensorflow-lite-support:0.4.7'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package com.example.upcreader;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -12,6 +19,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.journeyapps.barcodescanner.BarcodeCallback;
|
||||
import com.journeyapps.barcodescanner.BarcodeResult;
|
||||
@@ -24,10 +32,22 @@ public class CameraActivity extends AppCompatActivity implements BarcodeCallback
|
||||
|
||||
private DecoratedBarcodeView barcodeView;
|
||||
private Button scanButton;
|
||||
private Button copyShareButton;
|
||||
private TextView resultTextView;
|
||||
private boolean isScanning = false;
|
||||
private List<String> scannedCodes = new ArrayList<>();
|
||||
|
||||
// For product image display
|
||||
private View productImageView;
|
||||
private ImageView imageViewProduct;
|
||||
private TextView textViewProductName;
|
||||
|
||||
// For object recognition
|
||||
private ObjectRecognizer objectRecognizer;
|
||||
private View recognitionResultView;
|
||||
private ImageView imageViewRecognitionResult;
|
||||
private TextView textViewRecognitionResult;
|
||||
|
||||
private static final int PERMISSION_REQUEST_CAMERA = 1;
|
||||
|
||||
@Override
|
||||
@@ -37,8 +57,12 @@ public class CameraActivity extends AppCompatActivity implements BarcodeCallback
|
||||
|
||||
barcodeView = findViewById(R.id.barcode_scanner_view);
|
||||
scanButton = findViewById(R.id.button_scan);
|
||||
copyShareButton = findViewById(R.id.button_copy_share);
|
||||
resultTextView = findViewById(R.id.text_view_results);
|
||||
|
||||
// Initialize object recognizer
|
||||
objectRecognizer = new ObjectRecognizer(this);
|
||||
|
||||
// Check for camera permission
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
|
||||
@@ -56,6 +80,78 @@ public class CameraActivity extends AppCompatActivity implements BarcodeCallback
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
copyShareButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (scannedCodes.isEmpty()) {
|
||||
Toast.makeText(CameraActivity.this, "No codes to copy or share", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the text to copy/share
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String code : scannedCodes) {
|
||||
sb.append(code).append("\n");
|
||||
}
|
||||
String textToCopy = sb.toString().trim();
|
||||
|
||||
// Copy to clipboard
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("UPC Codes", textToCopy);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
|
||||
// Show toast notification
|
||||
Toast.makeText(CameraActivity.this, "Copied to clipboard", Toast.LENGTH_SHORT).show();
|
||||
|
||||
// Share via intent
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType("text/plain");
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, textToCopy);
|
||||
startActivity(Intent.createChooser(shareIntent, "Share UPC Codes"));
|
||||
}
|
||||
});
|
||||
|
||||
// Add touch listener to barcode view for object recognition
|
||||
barcodeView.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
// Get the touch coordinates
|
||||
float x = event.getX();
|
||||
float y = event.getY();
|
||||
|
||||
// Get the bitmap from the barcode view
|
||||
Bitmap bitmap = barcodeView.getBitmap();
|
||||
if (bitmap != null) {
|
||||
// Calculate the crop region around the touch point
|
||||
int cropSize = 224; // Size expected by the model
|
||||
int centerX = (int) x;
|
||||
int centerY = (int) y;
|
||||
|
||||
// Ensure the crop region stays within bounds
|
||||
int left = Math.max(0, centerX - cropSize / 2);
|
||||
int top = Math.max(0, centerY - cropSize / 2);
|
||||
int right = Math.min(bitmap.getWidth(), left + cropSize);
|
||||
int bottom = Math.min(bitmap.getHeight(), top + cropSize);
|
||||
|
||||
// Create a cropped bitmap
|
||||
Bitmap croppedBitmap = Bitmap.createBitmap(bitmap, left, top, right - left, bottom - top);
|
||||
|
||||
// Recognize the object
|
||||
String recognitionResult = objectRecognizer.recognize(croppedBitmap);
|
||||
|
||||
// Display the result
|
||||
showRecognitionResult(croppedBitmap, recognitionResult);
|
||||
|
||||
// Clean up
|
||||
croppedBitmap.recycle();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupScanner() {
|
||||
@@ -96,6 +192,53 @@ public class CameraActivity extends AppCompatActivity implements BarcodeCallback
|
||||
runOnUiThread(() -> {
|
||||
resultTextView.setText("Found: " + code + "\nTotal: " + scannedCodes.size());
|
||||
});
|
||||
|
||||
// Look up product information via API
|
||||
UpcApiManager.getInstance().getProductByUpc(code, new UpcApiManager.UpcApiCallback() {
|
||||
@Override
|
||||
public void onSuccess(UpcProduct product) {
|
||||
runOnUiThread(() -> {
|
||||
// Create and show product image view
|
||||
if (productImageView == null) {
|
||||
productImageView = getLayoutInflater().inflate(R.layout.layout_product_image, null);
|
||||
imageViewProduct = productImageView.findViewById(R.id.image_view_product);
|
||||
textViewProductName = productImageView.findViewById(R.id.text_view_product_name);
|
||||
|
||||
// Add to activity layout
|
||||
((ViewGroup) findViewById(android.R.id.content)).addView(productImageView);
|
||||
}
|
||||
|
||||
// Display product image and name
|
||||
if (product.getImageUrl() != null && !product.getImageUrl().isEmpty()) {
|
||||
Glide.with(CameraActivity.this)
|
||||
.load(product.getImageUrl())
|
||||
.placeholder(R.drawable.ic_product_placeholder)
|
||||
.error(R.drawable.ic_product_error)
|
||||
.into(imageViewProduct);
|
||||
}
|
||||
|
||||
if (product.getName() != null && !product.getName().isEmpty()) {
|
||||
textViewProductName.setText(product.getName());
|
||||
} else {
|
||||
textViewProductName.setText("Product Information");
|
||||
}
|
||||
|
||||
// Hide product image after 3 seconds
|
||||
productImageView.postDelayed(() -> {
|
||||
if (productImageView != null && productImageView.getParent() != null) {
|
||||
((ViewGroup) productImageView.getParent()).removeView(productImageView);
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(CameraActivity.this, "Could not lookup product: " + error, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,4 +274,27 @@ public class CameraActivity extends AppCompatActivity implements BarcodeCallback
|
||||
super.onPause();
|
||||
barcodeView.pause();
|
||||
}
|
||||
|
||||
private void showRecognitionResult(Bitmap bitmap, String result) {
|
||||
// Create and show recognition result view
|
||||
if (recognitionResultView == null) {
|
||||
recognitionResultView = getLayoutInflater().inflate(R.layout.layout_object_recognition_result, null);
|
||||
imageViewRecognitionResult = recognitionResultView.findViewById(R.id.image_view_recognition_result);
|
||||
textViewRecognitionResult = recognitionResultView.findViewById(R.id.text_view_recognition_result);
|
||||
|
||||
// Add to activity layout
|
||||
((ViewGroup) findViewById(android.R.id.content)).addView(recognitionResultView);
|
||||
}
|
||||
|
||||
// Display the cropped bitmap and recognition result
|
||||
imageViewRecognitionResult.setImageBitmap(bitmap);
|
||||
textViewRecognitionResult.setText(result);
|
||||
|
||||
// Hide result after 3 seconds
|
||||
recognitionResultView.postDelayed(() -> {
|
||||
if (recognitionResultView != null && recognitionResultView.getParent() != null) {
|
||||
((ViewGroup) recognitionResultView.getParent()).removeView(recognitionResultView);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.example.upcreader;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
|
||||
import org.tensorflow.lite.Interpreter;
|
||||
import org.tensorflow.lite.support.image.ImageProcessor;
|
||||
import org.tensorflow.lite.support.image.TensorImage;
|
||||
import org.tensorflow.lite.support.image.ops.ResizeOp;
|
||||
import org.tensorflow.lite.support.image.ops.ResizeOp.ResizeMethod;
|
||||
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer;
|
||||
import org.tensorflow.lite.support.common.FileUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ObjectRecognizer {
|
||||
private static final String MODEL_PATH = "detect.tflite";
|
||||
private static final String LABELS_PATH = "labelmap.txt";
|
||||
private static final int INPUT_SIZE = 224;
|
||||
private static final int NUM_CLASSES = 1000; // Default for MobileNet
|
||||
|
||||
private Interpreter interpreter;
|
||||
private List<String> labels;
|
||||
|
||||
public ObjectRecognizer(Context context) {
|
||||
try {
|
||||
// Load the model file
|
||||
MappedByteBuffer model = FileUtil.loadMappedFile(context, MODEL_PATH);
|
||||
interpreter = new Interpreter(model);
|
||||
|
||||
// Load the labels
|
||||
labels = FileUtil.loadLabels(context, LABELS_PATH);
|
||||
|
||||
// Validate model
|
||||
if (labels.size() != NUM_CLASSES) {
|
||||
Log.w("ObjectRecognizer", "Label count mismatch: " + labels.size() + " vs " + NUM_CLASSES);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e("ObjectRecognizer", "Error loading model or labels", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String recognize(Bitmap bitmap) {
|
||||
if (interpreter == null || labels.isEmpty()) {
|
||||
return "Model not loaded";
|
||||
}
|
||||
|
||||
try {
|
||||
// Preprocess the image
|
||||
TensorImage inputImage = preprocessImage(bitmap);
|
||||
|
||||
// Create output buffer
|
||||
float[][] output = new float[1][NUM_CLASSES];
|
||||
|
||||
// Run inference
|
||||
interpreter.run(inputImage.getBuffer(), output);
|
||||
|
||||
// Get the best prediction
|
||||
int bestIndex = 0;
|
||||
float bestConfidence = 0;
|
||||
|
||||
for (int i = 0; i < NUM_CLASSES; i++) {
|
||||
if (output[0][i] > bestConfidence) {
|
||||
bestConfidence = output[0][i];
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the label with highest confidence
|
||||
if (bestConfidence > 0.1f) { // Minimum confidence threshold
|
||||
return labels.get(bestIndex);
|
||||
} else {
|
||||
return "Unknown object";
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("ObjectRecognizer", "Error during inference", e);
|
||||
return "Error recognizing object";
|
||||
}
|
||||
}
|
||||
|
||||
private TensorImage preprocessImage(Bitmap bitmap) {
|
||||
// Convert bitmap to TensorImage
|
||||
TensorImage tensorImage = TensorImage.fromBitmap(bitmap);
|
||||
|
||||
// Resize image to model input size
|
||||
ImageProcessor imageProcessor = new ImageProcessor.Builder()
|
||||
.add(new ResizeOp(INPUT_SIZE, INPUT_SIZE, ResizeMethod.BILINEAR))
|
||||
.build();
|
||||
|
||||
return imageProcessor.process(tensorImage);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (interpreter != null) {
|
||||
interpreter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.example.upcreader;
|
||||
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
import android.util.Log;
|
||||
|
||||
public class UpcApiManager {
|
||||
private static final String BASE_URL = "https://api.upcitemdb.com/";
|
||||
private static UpcApiManager instance;
|
||||
private UpcApiService apiService;
|
||||
|
||||
private UpcApiManager() {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
|
||||
apiService = retrofit.create(UpcApiService.class);
|
||||
}
|
||||
|
||||
public static synchronized UpcApiManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new UpcApiManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void getProductByUpc(String upc, UpcApiCallback callback) {
|
||||
Call<UpcProduct> call = apiService.getProductByUpc(upc);
|
||||
call.enqueue(new retrofit2.Callback<UpcProduct>() {
|
||||
@Override
|
||||
public void onResponse(Call<UpcProduct> call, retrofit2.Response<UpcProduct> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
callback.onSuccess(response.body());
|
||||
} else {
|
||||
callback.onError("Failed to get product information");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<UpcProduct> call, Throwable t) {
|
||||
Log.e("UpcApiManager", "API call failed", t);
|
||||
callback.onError("Network error: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface UpcApiCallback {
|
||||
void onSuccess(UpcProduct product);
|
||||
void onError(String error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.example.upcreader;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Path;
|
||||
|
||||
public interface UpcApiService {
|
||||
@GET("/api/product/{upc}")
|
||||
Call<UpcProduct> getProductByUpc(@Path("upc") String upc);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.example.upcreader;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class UpcProduct {
|
||||
@SerializedName("upc")
|
||||
private String upc;
|
||||
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
@SerializedName("description")
|
||||
private String description;
|
||||
|
||||
@SerializedName("image_url")
|
||||
private String imageUrl;
|
||||
|
||||
@SerializedName("brand")
|
||||
private String brand;
|
||||
|
||||
@SerializedName("category")
|
||||
private String category;
|
||||
|
||||
// Default constructor required for Gson
|
||||
public UpcProduct() {}
|
||||
|
||||
// Getters
|
||||
public String getUpc() {
|
||||
return upc;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getImageUrl() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public String getBrand() {
|
||||
return brand;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
// Setters
|
||||
public void setUpc(String upc) {
|
||||
this.upc = upc;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void setImageUrl(String imageUrl) {
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public void setBrand(String brand) {
|
||||
this.brand = brand;
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#333333" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:gravity="center">
|
||||
<vector android:width="48dp" android:height="48dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FF0000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5z"/>
|
||||
<path android:fillColor="#FF0000" android:pathData="M15.5,15.5L8.5,8.5M8.5,15.5L15.5,8.5" android:strokeWidth="2" android:strokeColor="#FF0000" android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#333333" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:gravity="center">
|
||||
<vector android:width="48dp" android:height="48dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5z"/>
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -31,4 +31,13 @@
|
||||
android:padding="16dp"
|
||||
android:background="#333333" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_copy_share"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Copy/Share Scanned Codes"
|
||||
android:textSize="16sp"
|
||||
android:layout_margin="16dp"
|
||||
android:backgroundTint="#2196F3" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#000000"
|
||||
android:gravity="center"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_view_recognition_result"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:scaleType="centerInside"
|
||||
android:background="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view_recognition_result"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textAlignment="center"
|
||||
android:padding="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#000000"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_view_product"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:scaleType="centerInside"
|
||||
android:background="#333333" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view_product_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textAlignment="center"
|
||||
android:padding="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
Reference in new issue
Block a user