Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/229.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Android 如何创建使用Firebase图像的RecyclerView_Android_Firebase_Android Recyclerview_Firebase Storage - Fatal编程技术网

Android 如何创建使用Firebase图像的RecyclerView

Android 如何创建使用Firebase图像的RecyclerView,android,firebase,android-recyclerview,firebase-storage,Android,Firebase,Android Recyclerview,Firebase Storage,我的Firebase存储中名为Icons的文件夹中有超过1.5K个图像,我想将这些图像加载到使用GridLayoutManager的RecyclerView中。显然,我只想请求加载这些图像,如果它们在Recyclerview中可见,否则这是对存储的读取的浪费,对吗 因此,我首先创建R.layout.card_icons_rcv_item xml布局,它在ConstraintLayout中包含一个ImageView,以表示我的RecyclerView中的每个单元格 然后,我创建了适配器类,您可以在

我的Firebase存储中名为Icons的文件夹中有超过1.5K个图像,我想将这些图像加载到使用GridLayoutManager的RecyclerView中。显然,我只想请求加载这些图像,如果它们在Recyclerview中可见,否则这是对存储的读取的浪费,对吗

因此,我首先创建R.layout.card_icons_rcv_item xml布局,它在ConstraintLayout中包含一个ImageView,以表示我的RecyclerView中的每个单元格

然后,我创建了适配器类,您可以在下面和onBindViewHolder内部看到,我正在尝试加载与相应RecyclerView单元格中的每个单元格关联的图像,但效果不好

首先,我只得到大约2张图像,或者大部分时间没有加载任何其他图像,其次,我可以看到Log.dAdapter,从firestore加载卡+位置的错误卡图标;语句被调用用于最后3个单元格,最后我在logcat中看到,所有这些对firebase的请求都是同时发出的,即使图像不在recyclerView中

更新

Recycler视图似乎成功加载了图标,但速度非常慢。所以我在文档里翻了翻,偶然发现了位图。文档说,由于我的图像视图比Firebase存储上的图像要小,我可能应该对它们进行下采样。正如您在我更新的onBindViewHolder方法中所看到的,我就是这样做的。图像加载速度明显加快,但仍然不够快。我还遇到了一个奇怪的异常,我似乎无法修复。我正在更新下面的问题:

[更新]以下是我的问题:

我如何仅请求从firebase加载可见单元格的图像,因为从我现在在Logcat中看到的情况来看,所做的请求等于getItemCount返回的数字。 在getItemCount方法中,我返回一个随机数50,因为我不知道如何返回数据库中的图像数。那里至少有1.5K个图像,数据库将慢慢变大。这是否会引起任何问题?如果是,我该如何克服? 如何使这些图像的下载速度更快? 下载onBindViewHolder方法中看到的临时文件中的图像后,我应该删除它们吗?还是填充recycler视图的图像视图需要它们? 在几次运行之后,没有对代码进行任何更改,我开始在Logcat中出现以下错误: E/AuthUI:发生登录错误。 com.firebase.ui.auth.FirebaseUiException:保存凭据时出错。 位于com.firebase.ui.auth.viewmodel.smartlock.SmartLockHandler$1.onCompleteSmartLockHandler.java:99 位于com.google.android.gms.tasks.zzj.run未知来源:4 位于android.os.Handler.handleCallbackHandler.java:883 在android.os.Handler.dispatchMessageHandler.java:100 在android.os.Looper.Looper.java:221 在android.app.ActivityThread.mainActivityThread.java:7520 在java.lang.reflect.Method.Invokenactive方法中 位于com.android.internal.os.RuntimeInit$MethodAndArgsCaller.RuntimeInit.java:539 位于com.android.internal.os.ZygoteInit.mainZygoteInit.java:950 原因:com.google.android.gms.common.api.ApiException:16:当前应用程序的保存提示被禁用。要恢复,请删除 此应用程序来自智能密码锁中的从不保存列表 此设备上所有帐户的设置。 在com.google.android.gms.common.internal.apieexceptionutil.fromStatuscom.google.android.gms:play services base@@17.1.0:4 在com.google.android.gms.common.internal.zai.zafcom.google.android.gms:play services base@@17.1.0:2 在com.google.android.gms.common.internal.zak.onCompletecom.google.android.gms:play services base@@17.1.0:6 位于com.google.android.gms.common.api.internal.basependingreult.zaacom.google.android.gms:play services base@@17.1.0:176 位于com.google.android.gms.common.api.internal.basependingreult.setResultcom.google.android.gms:play services base@@17.1.0:135 在com.google.android.gms.common.api.internal.BaseImplementation$ApiMethodImpl.setResultcom.google.android.gms:play services base@@17.1.0:36 位于com.google.android.gms.internal.auth-api.zzo.zzc已知来源:4 位于com.google.android.gms.internal.auth-api.zzv.dispatchTransactionUnknown 资料来源:9 位于com.google.android.gms.internal.auth-api.zzd.onTransactUnknown 资料来源:12 在android.os.Binder.execTransactionInternalBinder.java:1021 在android.os.Binder.execTransactionBinder.java:994

我试图以所有者身份登录的用户已根据我的Log.d语句成功通过身份验证,但由于某些奇怪的原因,它不允许我从中读取数据 火基。这个错误提到我应该在Google SmartLock中更改一个设置,但我向上帝发誓,我已经在手机上到处寻找该设置,但我找不到它。我也在谷歌上搜索过,但运气不好。你知道我在哪里可以找到那个场景吗

CardIconRCVAdapter.java:

public class CardIconsRCVAdapter extends RecyclerView.Adapter<CardIconsRCVAdapter.ViewHolder> {

    private LayoutInflater inflater;
    private Context mContext;
    private StorageReference storageReference;

    public CardIconsRCVAdapter(Context context,StorageReference storageRef) {
        mContext = context;
        inflater = LayoutInflater.from(context);
        storageReference = storageRef;
    }

    // stores and recycles views as they are scrolled off screen
    class ViewHolder extends RecyclerView.ViewHolder {
        ImageView iconImgView;

        ViewHolder(@NonNull View itemView) {
            super(itemView);
            iconImgView = itemView.findViewById(R.id.cardIcon);
        }
    }

    // Inflates the cell layout from xml when needed
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.card_icons_rcv_item,parent,false);
        return new ViewHolder(view);
    }

    // Binds the data to the views in each cell
    @Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
        //Log.d("Adapter","OnBindViewHolder called for #" + position);
        StorageReference iconRefJpeg = storageReference.child("Icons/icon" + position + ".jpeg");
        final StorageReference iconRefPng = storageReference.child("Icons/icon" + position + ".png");
        try {
            final File localFile = File.createTempFile("icon" + position, ".jpeg");
            iconRefJpeg.getFile(localFile).addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() {
                @Override
                public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) {
                    holder.iconImgView.setImageBitmap(decodeSampledBitmapFromResource(localFile.getAbsolutePath(), 150, 150));
                }
            }).addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    Log.d("AdapterError", "Failed fetching .jpeg image. Trying to fetch .png");
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static Bitmap decodeSampledBitmapFromResource(String filePath, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath,options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(filePath,options);
    }

    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        Log.d("CalcSampleSize","height = " + height + " width = " + width + " Image type : " + options.outMimeType);
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

    @Override
    public int getItemCount() {
        return 50;
    }
}
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/mainToolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:elevation="4dp"
        android:background="@color/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/toolbar_title"
            android:text="@string/app_name"
            android:textSize="18sp"
            android:textColor="@android:color/white"
            android:layout_gravity="center"/>

    </androidx.appcompat.widget.Toolbar>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/cardIconsRCV"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginStart="8dp"
        android:layout_marginTop="64dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/mainToolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>
在onActivityResult中,我只需创建一个从当前活动到我的主活动的新意图,如果登录成功,则返回RecyclerView,或者重新启动当前活动,直到用户登录

问题1和2

我建议您将所有从FirebaseStorage下载的图像URL列表存储在一个列表中,例如。消防商店。然后,您可以使用Android架构组件中的分页库逐页请求图像URL列表,并将其提交给回收商的适配器。如果您将URL存储在FireStore中,您可以在查询中使用startFrom和endAt方法来帮助分页。这也有助于解决您不知道正在加载到RecyclerView的项目数量的问题。你可以阅读更多关于

问题3和4

关于将图像下载到ImageView,有一些很好的图像加载库,您可以使用各种选项将图像缓存到磁盘上。一些流行的例子是,等等。它们可以节省您的时间,并帮助您更加专注于实现应用程序逻辑

问题5

从FirebaseUI github自述文件

默认情况下,FirebaseUI使用智能密码锁存储用户凭据,并在后续尝试时自动将用户登录到您的应用程序中。建议使用智能锁以提供最佳用户体验,但在某些情况下,您可能希望禁用智能锁以进行测试或开发


在AuthUI生成器中,您可能希望将setIsSmartLockEnabledtrue更改为setIsSmartLockEnabled!BuildConfig.DEBUG/*凭据*/,true/*提示*/或使用setIsSmartLockEnabledfalse将其完全关闭。您可以在此处了解更多信息

我建议您使用类似的图像加载库,或者只需将URL中的图像加载到您的图像视图中。非常感谢您的回复,我甚至不知道这个库存在。我会做些调查,然后再打给你。顺便问一下,你知道我第五个问题中的错误吗?对于第五个问题,你能用你如何登录一个用户来更新这个问题吗?刚刚在我的帖子上添加了相应的代码。在帖子的底部:答案已经更新了。我还建议不要在使用AuthUI登录失败时重新启动整个活动,您可以向用户显示Toast或Snackbar之类的内容,然后重新启动AuthUI实例。您可以将startActivityForResult块移动到方法startAuthUI中,并在onCreate中调用该方法,然后调用将启动AuthUI意图的方法当AuthUI返回失败的登录响应时,用户再次尝试登录,因为重新启动活动的目的是重新启动AuthUI。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/mainToolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:elevation="4dp"
        android:background="@color/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/toolbar_title"
            android:text="@string/app_name"
            android:textSize="18sp"
            android:textColor="@android:color/white"
            android:layout_gravity="center"/>

    </androidx.appcompat.widget.Toolbar>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/cardIconsRCV"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginStart="8dp"
        android:layout_marginTop="64dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/mainToolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?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:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/cardIcon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="cardIcon"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>
class UserAuthenticationActivity : AppCompatActivity() {

    private val TAG = UserAuthenticationActivity::class.java.simpleName
    private val RC_SIGN_IN: Int = 101 // A request code used to authenticate users

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Choose authentication providers
        val providers = arrayListOf(
            AuthUI.IdpConfig.EmailBuilder().build(),
            AuthUI.IdpConfig.GoogleBuilder().build()
        )

        // Create and launch sign-in intent
        startActivityForResult(
            AuthUI.getInstance()
                .createSignInIntentBuilder()
                .setAvailableProviders(providers)
                .setIsSmartLockEnabled(true)
                .setLogo(R.drawable.applogotmp) // Set logo drawable-temp
                .build(),
            RC_SIGN_IN)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == RC_SIGN_IN) {
            val response = IdpResponse.fromResultIntent(data)

            if (resultCode == Activity.RESULT_OK) {
                // Successfully signed in
                // Consider passing the user object to the MainActivity using a bundle
                val user = FirebaseAuth.getInstance().currentUser
                user?.let {
                    // Name, email address, and profile photo Url
                    val name = user.displayName
                    val email = user.email
                    Log.d(TAG,"User $user just logged in. Name $name and email $email")
                }
                startActivity(Intent(this, MainActivity::class.java)) // User was authenticated, allow app access
                finish()
            } else { // Sign in failed
                if(response == null) { // If response is null the user canceled the sign-in flow using the back button.
                    startActivity(Intent(this,
                        UserAuthenticationActivity::class.java))// restart the activity/prompt for auth again
                    finish()
                } else {// Otherwise check response.getError().getErrorCode() and handle the error.
                    Log.d(TAG,"Error logging in user. Reason : ${response.error?.errorCode}")
                }
            }
        }
    }
}