i am generating pdf from recyclerView , but empty pdf is generated.Android Studio - android-recyclerview

i am generating pdf from recycler view, but getting empty pdf, probably recycler view is not getting converted to bitmap.
please help.
i have atttached the files here
the recycler view is properly shown but when pdf is generated , blank pdf is generated.
MainActivity.java
package com.example.recyclerview;
public class MainActivity extends AppCompatActivity {
// ArrayList for person names
ArrayList langNames = new ArrayList<>(Arrays.asList("Person 1", "Person 2", "Person 3", "Person 4", "Person 5", "Person 6", "Person 7","Person 8", "Person 9", "Person 10", "Person 11", "Person 12", "Person 13", "Person 14"));
ArrayList langText= new ArrayList<>(Arrays.asList("Person 1", "Person 2", "Person 3", "Person 4", "Person 5", "Person 6", "Person 7","Person 8", "Person 9", "Person 10", "Person 11", "Person 12", "Person 13", "Person 14"));
private Button btn;
private LinearLayout linear;
private Bitmap bitmap;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// get the reference of RecyclerView
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
// set a LinearLayoutManager with default horizontal orientation and false value for reverseLayout to show the items from start to end
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getApplicationContext(),LinearLayoutManager.VERTICAL,false);
recyclerView.setLayoutManager(linearLayoutManager);
// call the constructor of CustomAdapter to send the reference and data to Adapter
PDFadapter adapter = new PDFadapter(MainActivity.this, langNames,langText);
recyclerView.setAdapter(adapter); // set the Adapter to RecyclerView
linear=findViewById(R.id.linear);
btn=findViewById(R.id.btn1);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("size"," "+linear.getWidth()+" "+linear.getHeight());
bitmap=loadBitmap(adapter,linear.getWidth(),linear.getHeight());
try {
createPdf();
} catch (IOException e) {
e.printStackTrace();
}
}
});
// pdfConverter.createPdf((Context)MainActivity.this, (Activity)MainActivity.this);
}
private Bitmap loadBitmap(LinearLayout linear, int width, int height) {
Bitmap bitmap=Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
Canvas canvas =new Canvas(bitmap);
linear.draw(canvas);
return bitmap;
}
private void createPdf() throws IOException {
WindowManager windowManager=(WindowManager)getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dispalyMetrics=new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics(dispalyMetrics);
float width=dispalyMetrics.widthPixels;
float height=dispalyMetrics.heightPixels;
int convertwidth=(int)width;
int converthieght=(int)height;
PdfDocument document = new PdfDocument();
PdfDocument.PageInfo pageInfo=new PdfDocument.PageInfo.Builder(convertwidth,converthieght,1).create();
PdfDocument.Page page= document.startPage(pageInfo);
Canvas canvas= page.getCanvas();
Paint paint=new Paint();
canvas.drawPaint(paint);
bitmap=Bitmap.createScaledBitmap(bitmap,convertwidth,converthieght,true);
canvas.drawBitmap(bitmap,0,0,null);
document.finishPage(page);
File file=new File(getApplicationContext().getFilesDir(),"/abcd.pdf");
document.writeTo(new FileOutputStream(file));
document.close();
}
}
PDFadapter.java
public class PDFadapter extends RecyclerView.Adapter<PDFadapter.MyViewHolder> {
ArrayList langNames;
ArrayList langText;
Context context;
public PDFadapter(Context context, ArrayList langNames, ArrayList langText) {
this.context = context;
this.langNames = langNames;
this.langText= langText;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// infalte the item Layout
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout, parent, false);
// set the view's size, margins, paddings and layout parameters
MyViewHolder vh = new MyViewHolder(v); // pass the view to View Holder
return vh;
}
#Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
// set the data in items
holder.name.setText((String) langNames.get(position));
holder.text.setText((String) langText.get(position));
// implement setOnClickListener event on item view.
}
#Override
public int getItemCount() {
return langNames.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
// init the item view's
TextView name;
TextView text;
public MyViewHolder(View itemView) {
super(itemView);
// get the reference of item view's
name = (TextView) itemView.findViewById(R.id.name);
text = (TextView) itemView.findViewById(R.id.texts);
}
}
}
layout.xml
<?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="wrap_content"
android:id="#+id/linearlayout"
android:orientation="vertical">
<!--
items for a single row of RecyclerView
-->
<TextView
android:id="#+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColor="#color/black"
android:textSize="25sp" />
<TextView
android:id="#+id/texts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColor="#color/black"
android:textSize="18sp" />
</LinearLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/btn1"
/>
<LinearLayout
android:layout_width="match_parent"
android:id="#+id/linear"
android:orientation="vertical"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/recyclerView"
tools:context=".MainActivity"
/>
</LinearLayout>
</LinearLayout>
blank pdf generated

Related

How to crop image rectangle in camera preview on CameraX

I have a custom camera app which has a centered rectangle view, as you can see below:
When I take a picture I want to ignore everything outside the rectangle. And this is my XML layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black_50">
<TextureView
android:id="#+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_margin="16dp"
android:background="#drawable/rectangle"
app:layout_constraintBottom_toTopOf="#+id/cameraBottomView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/cameraBottomView"
android:layout_width="match_parent"
android:layout_height="130dp"
android:background="#color/black_50"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="#+id/cameraCaptureImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:src="#drawable/ic_capture_image"
app:layout_constraintBottom_toBottomOf="#id/cameraBottomView"
app:layout_constraintEnd_toEndOf="#id/cameraBottomView"
app:layout_constraintStart_toStartOf="#id/cameraBottomView"
app:layout_constraintTop_toTopOf="#id/cameraBottomView"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>
And this is my kotlin code for the cameraX preview:
class CameraFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_camera, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewFinder.post { setupCamera() }
}
private fun setupCamera() {
CameraX.unbindAll()
CameraX.bindToLifecycle(
this,
buildPreviewUseCase(),
buildImageCaptureUseCase(),
buildImageAnalysisUseCase()
)
}
private fun buildPreviewUseCase(): Preview {
val preview = Preview(
UseCaseConfigBuilder.buildPreviewConfig(
viewFinder.display
)
)
preview.setOnPreviewOutputUpdateListener { previewOutput ->
updateViewFinderWithPreview(previewOutput)
correctPreviewOutputForDisplay(previewOutput.textureSize)
}
return preview
}
private fun updateViewFinderWithPreview(previewOutput: Preview.PreviewOutput) {
val parent = viewFinder.parent as ViewGroup
parent.removeView(viewFinder)
parent.addView(viewFinder, 0)
viewFinder.surfaceTexture = previewOutput.surfaceTexture
}
/**
* Corrects the camera/preview's output to the display, by scaling
* up/down and/or rotating the camera/preview's output.
*/
private fun correctPreviewOutputForDisplay(textureSize: Size) {
val matrix = Matrix()
val centerX = viewFinder.width / 2f
val centerY = viewFinder.height / 2f
val displayRotation = getDisplayRotation()
val (dx, dy) = getDisplayScalingFactors(textureSize)
matrix.postRotate(displayRotation, centerX, centerY)
matrix.preScale(dx, dy, centerX, centerY)
// Correct preview output to account for display rotation and scaling
viewFinder.setTransform(matrix)
}
private fun getDisplayRotation(): Float {
val rotationDegrees = when (viewFinder.display.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> throw IllegalStateException("Unknown display rotation ${viewFinder.display.rotation}")
}
return -rotationDegrees.toFloat()
}
private fun getDisplayScalingFactors(textureSize: Size): Pair<Float, Float> {
val cameraPreviewRation = textureSize.height / textureSize.width.toFloat()
val scaledWidth: Int
val scaledHeight: Int
if (viewFinder.width > viewFinder.height) {
scaledHeight = viewFinder.width
scaledWidth = (viewFinder.width * cameraPreviewRation).toInt()
} else {
scaledHeight = viewFinder.height
scaledWidth = (viewFinder.height * cameraPreviewRation).toInt()
}
val dx = scaledWidth / viewFinder.width.toFloat()
val dy = scaledHeight / viewFinder.height.toFloat()
return Pair(dx, dy)
}
private fun buildImageCaptureUseCase(): ImageCapture {
val capture = ImageCapture(
UseCaseConfigBuilder.buildImageCaptureConfig(
viewFinder.display
)
)
cameraCaptureImageButton.setOnClickListener {
capture.takePicture(
FileCreator.createTempFile(JPEG_FORMAT),
Executors.newSingleThreadExecutor(),
object : ImageCapture.OnImageSavedListener {
override fun onImageSaved(file: File) {
requireActivity().runOnUiThread {
launchGalleryFragment(file.absolutePath)
}
}
override fun onError(
imageCaptureError: ImageCapture.ImageCaptureError,
message: String,
cause: Throwable?
) {
Toast.makeText(requireContext(), "Error: $message", Toast.LENGTH_LONG)
.show()
Log.e("CameraFragment", "Capture error $imageCaptureError: $message", cause)
}
})
}
return capture
}
private fun buildImageAnalysisUseCase(): ImageAnalysis {
val analysis = ImageAnalysis(
UseCaseConfigBuilder.buildImageAnalysisConfig(
viewFinder.display
)
)
analysis.setAnalyzer(
Executors.newSingleThreadExecutor(),
ImageAnalysis.Analyzer { image, rotationDegrees ->
Log.d(
"CameraFragment",
"Image analysis: $image - Rotation degrees: $rotationDegrees"
)
})
return analysis
}
private fun launchGalleryFragment(path: String) {
val action = CameraFragmentDirections.actionLaunchGalleryFragment(path)
findNavController().navigate(action)
}
}
And when I take the picture and send it into new page (GalleryPage), it's show all screen from the camera preview as you can see below:
And this is the kotlin code to get the picture from cameraX preview and display it into ImageView:
class GalleryFragment : Fragment() {
private lateinit var imageView: ImageView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_gallery, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
imageView = view.findViewById(R.id.img)
val imageFilePath = GalleryFragmentArgs.fromBundle(arguments!!).data
val bitmap = BitmapFactory.decodeFile(imageFilePath)
val rotatedBitmap = bitmap.rotate(90)
if (imageFilePath.isBlank()) {
Log.i(
"GalleryFragment",
"Image is Null or Empty"
)
} else {
Glide.with(activity!!)
.load(rotatedBitmap)
.into(imageView)
}
}
private fun Bitmap.rotate(degree:Int):Bitmap{
// Initialize a new matrix
val matrix = Matrix()
// Rotate the bitmap
matrix.postRotate(degree.toFloat())
// Resize the bitmap
val scaledBitmap = Bitmap.createScaledBitmap(
this,
width,
height,
true
)
// Create and return the rotated bitmap
return Bitmap.createBitmap(
scaledBitmap,
0,
0,
scaledBitmap.width,
scaledBitmap.height,
matrix,
true
)
}
}
Can somebody help me how to crop the image properly? Because I already search and research how to do it but still confused and not working for me.
I found a simple and straight forward way of doing this using camerax configuration.
Get the height and width of your rectangle shape of the preview area that you need from the camera preview.
For example
<View
android:background="#drawable/background_drawable"
android:id="#+id/border_view"
android:layout_gravity="center"
android:layout_width="350dp"
android:layout_height="100dp"/>
The width of mine is 350dp and a height of 100dp
Then use ViewPort to get the area you need
val viewPort = ViewPort.Builder(Rational(width, height), rotation).build()
//width = 350, height = 100, rotation = Surface.ROTATION_0
val useCaseGroup = UseCaseGroup.Builder()
.addUseCase(preview) //your preview
.addUseCase(imageAnalysis) //if you are using imageAnalysis
.addUseCase(imageCapture)
.setViewPort(viewPort)
.build()
Then bind to LifeCycle of CameraProvider
cameraProvider.bindToLifecycle(this, cameraSelector, useCaseGroup)
Use this link CropRect for more information
If you need any help comment below, I can provide you with the working source code.
Edit
Link to Source Code Sample
I have a solution, I just use this function to cropping the Image after capturing the Image:
private fun cropImage(bitmap: Bitmap, frame: View, reference: View): ByteArray {
val heightOriginal = frame.height
val widthOriginal = frame.width
val heightFrame = reference.height
val widthFrame = reference.width
val leftFrame = reference.left
val topFrame = reference.top
val heightReal = bitmap.height
val widthReal = bitmap.width
val widthFinal = widthFrame * widthReal / widthOriginal
val heightFinal = heightFrame * heightReal / heightOriginal
val leftFinal = leftFrame * widthReal / widthOriginal
val topFinal = topFrame * heightReal / heightOriginal
val bitmapFinal = Bitmap.createBitmap(
bitmap,
leftFinal, topFinal, widthFinal, heightFinal
)
val stream = ByteArrayOutputStream()
bitmapFinal.compress(
Bitmap.CompressFormat.JPEG,
100,
stream
) //100 is the best quality possibe
return stream.toByteArray()
}
Crop an image taking a reference a view parent like a frame and a view child like final reference
param bitmap image to crop
param frame where the image is set it
param reference frame to take reference for a crop the image
return image already cropped
You can see this example: https://github.com/rrifafauzikomara/CustomCamera/tree/custom_camerax
Here is an example of how I'm cropping an image taken by cameraX as you mentioned. I don't know if it the best way to do it and I'm interested to know other solutions.
camerax_version = "1.0.0-alpha07"
CameraFragment.java
Initialize cameraX :
// Views
private PreviewView previewView;
// CameraX
private ProcessCameraProvider cameraProvider;
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private CameraSelector cameraSelector;
private Executor executor;
private ImageCapture imageCapture;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
cameraProviderFuture = ProcessCameraProvider.getInstance(getContext());
executor = ContextCompat.getMainExecutor(getContext());
cameraSelector = new CameraSelector.Builder().requireLensFacing(LensFacing.BACK).build();
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
previewView = view.findViewById(R.id.preview);
ImageButton btnCapture = view.findViewById(R.id.btn_capture);
// Wait for the view to be properly laid out
previewView.post(() ->{
//Initialize CameraX
cameraProviderFuture.addListener(() -> {
if(cameraProvider != null) cameraProvider.unbindAll();
try {
cameraProvider = cameraProviderFuture.get();
// Set up the preview use case to display camera preview
Preview preview = new Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.setTargetRotation(previewView.getDisplay().getRotation())
.build();
preview.setPreviewSurfaceProvider(previewView.getPreviewSurfaceProvider());
// Set up the capture use case to allow users to take photos
imageCapture = new ImageCapture.Builder()
.setCaptureMode(ImageCapture.CaptureMode.MINIMIZE_LATENCY)
.setTargetRotation(previewView.getDisplay().getRotation())
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.build();
// Apply declared configs to CameraX using the same lifecycle owner
cameraProvider.bindToLifecycle(this, cameraSelector, preview,imageCapture);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, ContextCompat.getMainExecutor(getContext()));
});
btnCapture.setOnClickListener(v -> {
String format = "yyyy-MM-dd-HH-mm-ss-SSS";
SimpleDateFormat fmt = new SimpleDateFormat(format, Locale.US);
String date = fmt.format(System.currentTimeMillis());
File file = new File(getContext().getCacheDir(), date+".jpg");
imageCapture.takePicture(file, executor, imageSavedListener);
});
}
When a photo has been taken, open the gallery fragment passing the path of the photo :
private ImageCapture.OnImageSavedCallback imageSavedListener = new ImageCapture.OnImageSavedCallback() {
#Override
public void onImageSaved(#NonNull File photoFile) {
// Create new fragment and transaction
Fragment newFragment = new GalleryFragment();
FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
// Set arguments
Bundle args = new Bundle();
args.putString("KEY_PATH", Uri.fromFile(photoFile).toString());
newFragment.setArguments(args);
// Replace whatever is in the fragment_container view with this fragment,
transaction.replace(R.id.fragment_container, newFragment,null);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
}
#Override
public void onError(int imageCaptureError, #NonNull String message, #Nullable Throwable cause) {
if (cause != null) {
cause.printStackTrace();
}
}
};
At this moment, the photo has not been cropped, I don't know if it possible to do it directly with cameraX.
GalleryFragment.java
Load the argument passed to the fragment.
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String path = getArguments().getString("KEY_PATH");
sourceUri = Uri.parse(path);
}
Load the Uri with glide in an ImageView and then crop it.
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Initialize the views
ImageView imageView = view.findViewById(R.id.image_view);
View cropArea = view.findViewById(R.id.crop_area);
// Display the image
Glide.with(this).load(sourceUri).listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
// Get original bitmap
sourceBitmap = ((BitmapDrawable)resource).getBitmap();
// Create a new bitmap corresponding to the crop area
int[] cropAreaXY = new int[2];
int[] placeHolderXY = new int[2];
Rect rect = new Rect();
imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
#Override
public boolean onPreDraw() {
try {
imageView.getLocationOnScreen(placeHolderXY);
cropArea.getLocationOnScreen(cropAreaXY);
cropArea.getGlobalVisibleRect(rect);
croppedBitmap = Bitmap.createBitmap(sourceBitmap, cropAreaXY[0], cropAreaXY[1] - placeHolderXY[1], rect.width(), rect.height());
// Save the croppedBitmap if you wish
getActivity().runOnUiThread(() -> imageView.setImageBitmap(croppedBitmap));
return true;
}finally {
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
});
return false;
}
}).into(imageView);
}
fragment_camera.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black">
<androidx.camera.view.PreviewView
android:id="#+id/preview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="3:4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/crop_area"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="8dp"
android:background="#drawable/rectangle_round_corners"
app:layout_constraintBottom_toBottomOf="#+id/preview"
app:layout_constraintDimensionRatio="4.5:3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/cameraBottomView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/preview" />
<ImageButton
android:id="#+id/btn_capture"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="#drawable/ic_shutter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/preview" />
</androidx.constraintlayout.widget.ConstraintLayout>
fragment_gallery.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/layout_main"
android:background="#android:color/black"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/crop_area"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="4.5:3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
If you want the image to be cropped to whichever your PreviewView is showing, just do:
val useCaseGroup = UseCaseGroup.Builder()
.addUseCase(preview!!)
.addUseCase(imageCapture!!)
.setViewPort(previewView.viewPort!!)
.build()
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, useCaseGroup)

Android: Attempt to invoke virtual method on a null object reference in onBindViewHolder

My app is supposed to parse JSON data and display it in a recycler view. When I run the app, it crashes with the following error:
I've re-checked my code multiple times to make sure I am calling the correct resource files, but I still cant find the source of the issue. The logcat tells me the issue is in the onBindViewHolder method in the recycler view adapter class, but everything looks normal to me. Can anyone point me in the right direction? Here is the relevant code:
Recycler View Adapter:
public class IndRvAdapter extends RecyclerView.Adapter<IndRvAdapter.MyViewHolder> {
private Context context;
private List<Ind> indList;
public IndRvAdapter(Context context, List<Ind> indList) {
this.context = context;
this.indList = indList;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view;
LayoutInflater inflater = LayoutInflater.from(context);
view = inflater.inflate(R.layout.ind_card, viewGroup, false);
final MyViewHolder viewHolder = new MyViewHolder(view);
viewHolder.indView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent i = new Intent(context, SingleInd.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.putExtra("pacshort", indList.get(viewHolder.getAdapterPosition()).getPacshort());
i.putExtra("supopp", indList.get(viewHolder.getAdapterPosition()).getSuppopp());
i.putExtra("candname", indList.get(viewHolder.getAdapterPosition()).getCandname());
i.putExtra("district", indList.get(viewHolder.getAdapterPosition()).getDistrict());
i.putExtra("amount", indList.get(viewHolder.getAdapterPosition()).getAmount());
i.putExtra("party", indList.get(viewHolder.getAdapterPosition()).getExpParty());
i.putExtra("payee", indList.get(viewHolder.getAdapterPosition()).getPayee());
i.putExtra("date", indList.get(viewHolder.getAdapterPosition()).getDate());
i.putExtra("origin", indList.get(viewHolder.getAdapterPosition()).getOrigin());
i.putExtra("source", indList.get(viewHolder.getAdapterPosition()).getSource());
context.startActivity(i);
}
});
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder myViewHolder, int i) {
myViewHolder.pacshorts.setText(indList.get(i).getPacshort());
myViewHolder.supOpp.setText(indList.get(i).getSuppopp());
myViewHolder.candName.setText(indList.get(i).getCandname());
myViewHolder.district.setText(indList.get(i).getDistrict());
}
#Override
public int getItemCount() {
return indList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder{
TextView pacshorts, supOpp, candName, district;
LinearLayout indView;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
pacshorts = itemView.findViewById(R.id.tv_pacshort);
supOpp = itemView.findViewById(R.id.tv_suppopp);
candName = itemView.findViewById(R.id.tv_candname);
district = itemView.findViewById(R.id.tv_district);
indView = itemView.findViewById(R.id.ind_view);
}
}
}
Card View resource:
<?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="wrap_content"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:id="#+id/ind_view">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_gravity="center"
android:layout_margin="5dp"
android:elevation="3dp"
card_view:cardCornerRadius="15dp"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimaryDark">
<TextView
android:id="#+id/pacshort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="TextView" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/pacshort"
android:id="#+id/tv_suppop"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tv_candname"
android:layout_below="#+id/tv_suppop"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tv_district"
android:layout_below="#+id/tv_candname"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
Recycler View:
<?xml version="1.0" encoding="utf-8"?>
<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"
android:background="#color/background_color"
tools:context=".IndExpend">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/ind_rv">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
Main Activity that parses the JSON data
public class IndExpend extends AppCompatActivity {
/*
* Open secrets API: Returns the 50 latest independent expenditures transactions
* Updated every 4 days
*/
private static final String url = "http://www.opensecrets.org/api/?method=independentExpend&output=json&apikey=d1ff8f708ca0745d75e9ffa0ee6f3d09";
private LinearLayoutManager linearLayoutManager;
private List <Ind> indList;
private RecyclerView myrv;
private RecyclerView.Adapter adapter;
public IndExpend(){}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ind_expend);
linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
myrv = findViewById(R.id.ind_rv);
indList = new ArrayList<>();
adapter = new IndRvAdapter(this, indList);
myrv.setHasFixedSize(true);
myrv.setLayoutManager(linearLayoutManager);
myrv.setAdapter(adapter);
getData();
}
private void getData() {
final ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setMessage("Loading...");
progressDialog.show();
StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
progressDialog.dismiss();
try {
JSONObject object = new JSONObject (response);
JSONObject responseObj = object.getJSONObject("response");
JSONArray array = responseObj.getJSONArray("indexp");
for (int i = 0; i < array.length(); i++){
JSONObject attributesObj = array.getJSONObject(i).getJSONObject("#attributes");
Ind ind = new Ind(attributesObj.getString("pacshort"),
attributesObj.getString("suppopp"),
attributesObj.getString("candname"),
attributesObj.getString("district"),
attributesObj.getString("amount"),
attributesObj.getString("party"),
attributesObj.getString("payee"),
attributesObj.getString("date"),
attributesObj.getString("origin"),
attributesObj.getString("source"));
indList.add(ind);
}
adapter = new IndRvAdapter(getApplicationContext(), indList);
myrv.setAdapter(adapter);
}catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("Volley", error.toString());
progressDialog.dismiss();
}
});
RequestQueue requestQueue = Volley.newRequestQueue(this );
requestQueue.add(stringRequest);
}
}
Can anyone help me find the source of the problem?
I think it is because you are trying to bind the view with this id:
pacshorts = itemView.findViewById(R.id.tv_pacshort);
But on the layout file the id for that element its just pacshort:
<TextView
android:id="#+id/pacshort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="TextView" />
Generally on these type of null pointer exceptions when working on Android happens for one of two reasons. First, the res file that's being inflated is not the right one or, second, the id used to bind the views is misspelled or doesn't exist in that view.

How to apply an onClick Listener to an imageView within a CardView

I'm trying to setup an onClick Listener where that listener already has a button. The purpose is to provide a second way to access an intent (in this case a "ReadActivity" class. An example of this is
Here is my code:
#Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
holder.tv_book_title.setText(mData.get(position).getBookTitle());
holder.tv_book_author.setText("by " + mData.get(position).getBookAuthor());
holder.img_book_thumbnail.setImageResource(mData.get(position).getBookId());
holder.actionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(mContext, ReadActivity.class);
// passing data to the book activity
intent.putExtra("Id", mData.get(position).getPhotoId());
intent.putExtra("Title", mData.get(position).getBookTitle());
intent.putExtra("Author", mData.get(position).getBookAuthor());
intent.putExtra("Description", mData.get(position).getContentUrl());
intent.putExtra("Thumbnail", mData.get(position).getBookId());
// start the activity
mContext.startActivity(intent);
}
});
And this is my cardview_item xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="#dimen/card_height"
android:layout_gravity="center"
android:layout_marginBottom="#dimen/md_keylines"
android:layout_marginLeft="#dimen/md_keylines"
android:layout_marginRight="#dimen/md_keylines"
android:foreground="?attr/selectableItemBackground">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/book_img_id"
**android:onClick="imageClick"**
android:layout_width="match_parent"
android:layout_height="#dimen/card_image_height"
android:scaleType="centerCrop"
android:contentDescription="#string/todo" />
<TextView
android:id="#+id/book_title_id"
android:layout_width="match_parent"
android:layout_height="#dimen/card_title_height"
android:layout_alignBottom="#+id/book_img_id"
android:layout_marginStart="#dimen/md_keylines"
android:textAppearance="#style/TextAppearance.AppCompat.Title"
android:textColor="#color/white" />
<TextView
android:id="#+id/book_author_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/book_img_id"
android:layout_marginLeft="#dimen/md_keylines"
android:layout_marginTop="#dimen/md_keylines"
android:layout_marginBottom="#dimen/md_keylines"
android:layout_marginRight="#dimen/md_keylines"
android:ellipsize="end"
android:singleLine="true"
android:textColor="#color/dark_grey"
android:textSize="#dimen/article_subheading" />
<Button
android:id="#+id/action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/book_author_id"
style="?android:attr/borderlessButtonStyle"
android:textColor="?attr/colorPrimary"
android:text="#string/read" />
</RelativeLayout>
</android.support.v7.widget.CardView>
In the xml, I tried to set a onClick line, but I'm not sure how to setup the onClick listener. Is there a way to do this without affecting the originial code? Or would it be easier to copy to actionButton.setOnClickListener and just change the holder setting to the thumbnail?
You should not use `android:onClick" attribute for handling click. You better handle it in code so that your Layout and logic not tightly coupled.
You need to use setOnClickListener like at the actionButton. Or you can slightly change your code to handle the click inside your ViewHolder. Change your code like this:
// use View.OnClickListener so that the click listener won't
// be recreated when the View is recycled
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public Button actionButton;
public ImageView img_book_thumbnail;
public ViewHolder(Context context, View itemView) {
super(itemView);
// find actionButton and img_book_thumbnail
...
// Attach a click listener to both of them
actionButton.setOnClickListener(this);
img_book_thumbnail.setOnClickListener(this);
}
// Handles clicked
#Override
public void onClick(View view) {
if(view.getId == R.id.action_button) {
// do something
} else if(view.getId == R.id.book_img_id) {
// do something
}
}
}
Instead of directly handling the click in Adapter, you should delegate the click handling to the parent activity / fragment of the Adapter by using a Listener/Callback mechanism.

Why doesn't my Viewholder work?

So I'm creating a List for which I'm using my own implementation of the array adapter. I'm also using a viewholder for performance. But for some reason I get a nullpointer exception at this line:
holder.icon.setImageResource(R.drawable.no);
So I guess my holder isn't initialized? But I don't see how else my code should be?
public class NameViewAdapter extends ArrayAdapter<String>{
private String[] names;
private static class ViewHolder{
ImageView icon=null;
}
public NameViewAdapter(Context context, int textViewResourceId,String[] names) {
super(context, textViewResourceId, names);
this.names = names;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView==null)
{
LayoutInflater li =
(LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = li.inflate(R.layout.row,parent,false);
holder=new ViewHolder();
holder.icon=(ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
if (names[position].length() > 4)
holder.icon.setImageResource(R.drawable.ok);
else
holder.icon.setImageResource(R.drawable.no);
return convertView;
}
}
This is my XML-file:
<?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">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/imageView"
android:src="#drawable/ic_launcher"/>
<TextView
android:id="#+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
K so it seemed that the next line:
holder.icon=(ImageView) convertView.findViewById(R.id.icon);
should be changed to
holder.icon=(ImageView) convertView.findViewById(R.id.imageView);
cause that's the id the ImageView has in the xml file

QuickReturn with RecyclerView

I am trying to have a CheckBox (which is above the RecyclerView) as QuickReturn. The simple idea would be to hide the checkbox, when scroling up and make it visible, when scrolling down. (as in https://github.com/dbleicher/recyclerview-grid-quickreturn.)
And, it kind of works, but I have a strange flickering when scrolling.
Any thoughts on that?
MainActivity.java:
public class MainActivity extends ActionBarActivity {
private CheckBox mCheckbox;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCheckbox = (CheckBox) findViewById(R.id.checkbox);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recview);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
// Simple check if moved vertically.
// React to scrolls of a minimum amount (3, in this case)
if (dy > 3) {
if (mCheckbox.getVisibility() == View.VISIBLE) {
mCheckbox.setVisibility(View.GONE);
}
} else if (dy < -3) {
if (mCheckbox.getVisibility() == View.GONE) {
mCheckbox.setVisibility(View.VISIBLE);
}
}
}
});
List<String> dataset = Arrays.asList("one", "two", "three", "four", "five");
CustomAdapter adapter = new CustomAdapter(dataset);
recyclerView.setAdapter(adapter);
}
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
private List<String> mDataset;
public CustomAdapter(List<String> dataset) {
mDataset = dataset;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup arg0, int arg1) {
View v = LayoutInflater.from(arg0.getContext())
.inflate(R.layout.address_card, arg0, false);
return new ViewHolder(v);
}
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.mText.setText(mDataset.get(position));
}
#Override
public int getItemCount() {
return mDataset.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView mText;
public ViewHolder(View v) {
super(v);
mText = (TextView) v.findViewById(R.id.cardText);
}
}
}
}
activity_main.xml:
<LinearLayout 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"
android:orientation="vertical"
tools:context="${relativePackage}.${activityClass}" >
<CheckBox
android:id="#+id/checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Check me" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical" />
</LinearLayout>
adress_card.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:elevation="#dimen/cardview_default_elevation"
card_view:cardCornerRadius="#dimen/cardview_default_radius" >
<TextView
android:id="#+id/cardText"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:text="TextView" />
</android.support.v7.widget.CardView>
In your activity_main.xml, you have both the checkbox and RV in a LinearLayout. Whenever you set the checkbox to View.GONE, not only does it remove that view, but it has to recalculate the layout of the RV (and grow it to full_size). That's a lot of work on the main thread. Instead of a LinearLayout, add both items to a FrameLayout (RV first, then the Checkbox). This will make the checkbox float on-top of the RV, but the RV size will no longer change when you hide the checkbox. I suspect this will eliminate the flickering.
<FrameLayout 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="${relativePackage}.${activityClass}" >
<android.support.v7.widget.RecyclerView
android:id="#+id/recview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<CheckBox
android:id="#+id/checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Check me" />
</FrameLayout>
Using Gravity settings, you can position the checkbox to where you want it to appear (in front of the RV). If you want it at the top, and you don't want it to obscure the top-most items in the RV (when it's scrolled to the top), the look at the ItemDecorator class (QRBarDecoration) in the same project you reference above.
Hope that helps.